Recherche en texte intégral dans MongoDB

MongoDB, l'une des principales bases de données NoSQL, est réputée pour ses performances rapides, son schéma flexible, son évolutivité et ses excellentes capacités d'indexation. Les index MongoDB sont au cœur de ces performances rapides. Ils permettent une exécution efficace des requêtes en évitant les analyses de la collection complète et, partant, en limitant le nombre de documents. Les recherches effectuées par MongoDB. 

À partir de la version 2.4, MongoDB a commencé avec une fonctionnalité expérimentale prenant en charge Recherche en texte intégral en utilisant Index de texte. Cette fonctionnalité fait désormais partie intégrante du produit (et n'est plus une fonctionnalité expérimentale). Dans cet article, nous allons explorer les fonctionnalités de recherche en texte intégral de MongoDB à partir des principes fondamentaux..

Si vous êtes nouveau sur MongoDB, je vous recommande de lire les articles suivants sur Envato Tuts + qui vous aideront à comprendre les concepts de base de MongoDB:

  • Premiers pas avec MongoDB - Partie 1
  • Mappage de bases de données relationnelles et SQL sur MongoDB 

Les bases

Avant d'entrer dans les détails, examinons le contexte. La recherche en texte intégral fait référence à la technique de recherche d'un base de données en texte intégral par rapport aux critères de recherche spécifiés par l'utilisateur. Cela ressemble à la façon dont nous recherchons tout contenu sur Google (ou en fait toute autre application de recherche) en saisissant certains mots clés / expressions de chaîne et en récupérant les résultats pertinents triés par leur classement..

Voici quelques scénarios supplémentaires dans lesquels une recherche en texte intégral serait en cours:

  • Pensez à rechercher votre sujet favori sur le wiki. Lorsque vous saisissez un texte de recherche sur Wiki, le moteur de recherche affiche les résultats de tous les articles liés aux mots-clés / expressions recherchés (même si ces mots-clés ont été utilisés profondément dans l'article). Ces résultats de recherche sont triés par pertinence en fonction du score obtenu..  
  • Comme autre exemple, considérons un site de réseau social sur lequel l’utilisateur peut effectuer une recherche pour trouver tous les articles contenant le mot clé. chatsen eux ou pour être plus complexe, tous les messages qui ont des commentaires contenant le mot chats.  

Avant de poursuivre, vous devez connaître certains termes généraux relatifs à la recherche en texte intégral. Ces termes sont applicables à toute implémentation de recherche de texte intégral (et non spécifique à MongoDB).

Mots d'arrêt

Les mots vides sont les mots non pertinents qui doivent être filtrés du texte. Par exemple: a, an, the, is, at, which, etc..

Stemming

La création de racines est le processus de réduction des mots à leur racine. Par exemple: des mots tels que se tenir debout, se tenir debout, se tenir debout, etc. ont une base commune.

Notation

Un classement relatif pour mesurer lequel des résultats de recherche est le plus pertinent.  

Alternatives à la recherche en texte intégral dans MongoDB

Avant que MongoDB ne propose le concept des index de texte, nous modélisions nos données pour prendre en charge les recherches par mot clé ou utilisions des expressions régulières pour implémenter de telles fonctionnalités de recherche. Cependant, l’utilisation de l’une de ces approches avait ses propres limites:

  • Tout d’abord, aucune de ces approches ne prend en charge des fonctionnalités telles que l’accrochage, les mots vides, le classement, etc..  
  • L'utilisation de recherches par mot-clé nécessiterait la création d'index à plusieurs clés, ce qui n'est pas suffisant par rapport au texte intégral..
  • L'utilisation d'expressions régulières n'est pas efficace du point de vue des performances, car ces expressions n'utilisent pas efficacement les index.
  • De plus, aucune de ces techniques ne peut être utilisée pour effectuer des recherches de phrases (comme la recherche de "films sortis en 2015") ou des recherches pondérées..  

Outre ces approches, il existe des solutions alternatives, telles que Elastic Search ou SOLR, pour des applications plus avancées et plus complexes axées sur la recherche. Mais l’utilisation de l’une de ces solutions accroît la complexité architecturale de l’application, car MongoDB doit maintenant communiquer avec une base de données externe supplémentaire.. 

Notez que la recherche en texte intégral de MongoDB n'est pas proposée en remplacement complet des bases de données de moteurs de recherche telles qu'Elastic, SOLR, etc. Cependant, elle peut être utilisée efficacement pour la majorité des applications construites avec MongoDB aujourd'hui..

Présentation de la recherche de texte MongoDB

A l'aide de la recherche en texte intégral MongoDB, vous pouvez définir un index de texte sur tout champ du document dont la valeur est une chaîne ou un tableau de chaînes. Lorsque nous créons un index de texte sur un champ, MongoDB tokenize et enregistre le contenu du texte du champ indexé, et configure les index en conséquence..  

Pour mieux comprendre les choses, passons maintenant à des choses pratiques. Je veux que vous suiviez le tutoriel avec moi en essayant les exemples dans shell mongo. Nous allons d’abord créer des exemples de données que nous utiliserons tout au long de l’article, puis nous discuterons des concepts clés..

Aux fins de cet article, considérons une collection messages qui stocke des documents de la structure suivante: 

"sujet": "Joe a un chien", "contenu": "Les chiens sont les meilleurs amis de l'homme", "aime": 60, "année": 2015, "langue": "anglais"

Insérons quelques exemples de documents en utilisant le insérer commande pour créer nos données de test:

db.messages.insert ("sujet": "Joe a un chien", "contenu": "Les chiens sont les meilleurs amis de l'homme", "aime": 60, "année": 2015, "langue": "anglais" ) db.messages.insert ("subject": "Les chiens mangent les chats et les chiens mangent aussi les pigeons" "," content ":" Les chats ne sont pas des méchants "," aime ": 30," année ": 2015," langue ": "english") db.messages.insert ("sujet": "Les chats mangent les rats", "content": "Les rats ne cuisinent pas les aliments", "aime": 55, "année": 2014, "langue": "english") db.messages.insert ("subject": "Les rats mangent Joe", "content": "Joe mange un rat", "aime": 75, "année": 2014, "langue": " Anglais")

Création d'un index de texte

Un index de texte est créé de manière assez similaire à la façon dont nous créons un index normal, sauf qu’il spécifie la texte mot clé au lieu de spécifier un ordre croissant / décroissant.

Indexer un seul champ

Créer un index de texte sur le assujettir champ de notre document en utilisant la requête suivante:

db.messages.createIndex ("subject": "text")

Pour tester cet index de texte nouvellement créé sur le assujettir champ, nous allons rechercher des documents en utilisant le $ text opérateur. Nous rechercherons tous les documents qui ont le mot clé chiens dans leurs assujettir champ. 

Étant donné que nous effectuons une recherche de texte, nous souhaitons également obtenir des statistiques sur la pertinence des documents résultants. Pour ce faire, nous utiliserons le $ Meta: "textScore" expression, qui fournit des informations sur le traitement des données $ text opérateur. Nous allons également trier les documents par leur textScore en utilisant le Trier commander. Un plus textScore indique une correspondance plus pertinente. 

db.messages.find ($ text: $ search: "dogs", score: $ meta: "toextScore"). sort (score: $ meta: "textScore")

La requête ci-dessus renvoie les documents suivants contenant le mot clé chiens dans leurs assujettir champ. 

"_id": ObjectId ("55f4a5d9b592880356441e94"), "subject": "Les chiens mangent les chats et les chiens mangent aussi les pigeons", "content": "Les chats ne sont pas des méchants", "aime": 30, "année": 2015, "language": "english", "score": 1 "_id": ObjectId ("55f4a5d9b592880356441e93"), "subject": "Joe possède un chien", "content": "Les chiens sont les meilleurs amis de l'homme", " aime ": 60," année ": 2015," langue ":" anglais "," partition ": 0.6666666666666666

Comme vous pouvez le constater, le premier document a un score de 1 (puisque le mot clé chien apparaît deux fois dans son sujet) par opposition au deuxième document avec un score de 0,66. La requête a également trié les documents renvoyés dans l'ordre décroissant de leur score..

Une question qui peut se poser dans votre esprit est que si nous recherchons le mot clé chiens, pourquoi le moteur de recherche prend le mot clé chien (sans 's') en considération? Vous souvenez-vous de notre discussion sur le stemming, où les mots clés de recherche sont réduits à leur base? C’est la raison pour laquelle le mot clé chiens est réduit à chien.

Indexation de plusieurs champs (index composé)

Plus souvent qu'autrement, vous utiliserez la recherche de texte sur plusieurs champs d'un document. Dans notre exemple, nous activerons l’indexation de texte composé sur le assujettir et contenu des champs. Allez-y et exécutez la commande suivante dans le shell mongo:  

db.messages.createIndex ("subject": "text", "content": "text")

Cela a-t-il fonctionné? Non!! La création d'un deuxième index de texte vous donnera un message d'erreur indiquant qu'un index de recherche en texte intégral existe déjà. Pourquoi est-ce? La réponse est que les index de texte sont limités à un seul index par collection. Par conséquent, si vous souhaitez créer un autre index de texte, vous devrez supprimer l'existant et recréer le nouvel.. 

db.messages.dropIndex ("subject_text") db.messages.createIndex ("subject": "text", "content": "text") 

Après avoir exécuté les requêtes de création d’index ci-dessus, essayez de rechercher tous les documents avec un mot clé. chat.

db.messages.find ($ text: $ search: "cat", score: $ meta: "textScore"). sort (score: $ meta: "textScore")

La requête ci-dessus générerait les documents suivants:

"_id": ObjectId ("55f4af22b592880356441ea4"), "sujet": "Les chiens mangent les chats et les chiens mangent aussi les pigeons", "content": "Les chats ne sont pas méchants", "aime": 30, "année": 2015, "language": "english", "score": 1.3333333333333335 "_id": ObjectId ("55f4af22b592880356441ea5"), "subject": "Les chats mangent les rats", "le contenu": "Les rats ne cuisinent pas les aliments", "aime ": 55 ans," année ": 2014," langue ":" anglais "," partition ": 0.6666666666666666 

Vous pouvez voir que la partition du premier document, qui contient le mot clé chat à la fois assujettir et contenu champs, est plus élevé. 

Indexation de l'intégralité du document (Indexation générique)

Dans le dernier exemple, nous avons mis un index combiné sur le assujettir et contenu des champs. Mais il peut exister des scénarios dans lesquels vous souhaitez que le contenu textuel de vos documents soit interrogeable.. 

Par exemple, envisagez de stocker des courriels dans des documents MongoDB. Dans le cas d'e-mails, tous les champs, y compris l'expéditeur, le destinataire, le sujet et le corps, doivent être interrogeables. Dans de tels scénarios, vous pouvez indexer tous les champs de chaîne de votre document en utilisant le $ ** spécificateur générique.

La requête ressemblerait à ceci (assurez-vous de supprimer l'index existant avant d'en créer un nouveau):

db.messages.createIndex ("$ **": "text")

Cette requête définirait automatiquement des index de texte sur tous les champs de chaîne de nos documents. Pour tester ceci, insérez un nouveau document avec un nouveau champ emplacement dedans:

db.messages.insert ("sujet": "Les oiseaux peuvent cuisiner", "contenu": "Les oiseaux ne mangent pas de rats", "aime": 12, "année": 2013, emplacement: "Chicago", "langue" :"Anglais")

Maintenant, si vous essayez la recherche de texte avec un mot clé Chicago (requête ci-dessous), il retournera le document que nous venons d'insérer.

db.messages.find ($ text: $ search: "chicago", score: $ meta: "textScore"). sort (score: $ meta: "textScore")

Quelques points sur lesquels j'aimerais m'attarder:

  • Notez que nous n’avons pas défini explicitement un index sur le emplacement champ après avoir inséré un nouveau document. En effet, nous avons déjà défini un index de texte sur l’ensemble du document à l’aide de la touche $ ** opérateur.
  • Les index génériques peuvent parfois être lents, en particulier dans les scénarios où vos données sont très volumineuses. Pour cette raison, planifiez judicieusement vos index de document (également appelés index génériques), car ils risquent de nuire aux performances..

Recherche avancée

Recherche de phrase

Vous pouvez rechercher des expressions telles que «oiseaux intelligents qui aiment cuisiner»en utilisant des index de texte. Par défaut, la recherche par phrase crée une OU recherche sur tous les mots-clés spécifiés, c’est-à-dire qu’il recherchera les documents contenant les mots-clés intelligent, oiseau, amour ou cuisinier.

db.messages.find ($ text: $ search: "les oiseaux intelligents qui cuisinent", score: $ meta: "text Score"). sort (score: $ meta: "text Score ")

Cette requête produirait les documents suivants:

"_id": ObjectId ("55f5289cb592880356441ead"), "sujet": "Les oiseaux peuvent cuisiner", "contenu": "Les oiseaux ne mangent pas de rats", "aime": 12, "année": 2013, "lieu": "Chicago", "langue": "anglais", "partition": 2 "_id": ObjectId ("55f5289bb592880356441eab"), "sujet": "Les chats mangent les rats", "contenu": "Les rats ne cuisinent pas les aliments "," aime ": 55," année ": 2014," langue ":" anglais "," partition ": 0.6666666666666666 

Si vous souhaitez effectuer une recherche de phrase exacte (logique ET), vous pouvez le faire en spécifiant des guillemets dans le texte de recherche. 

db.messages.find ($ text: $ search: "\" cook food \ "", score: $ meta: "textScore"). sort (score: $ meta: "textScore ")

Cette requête aboutirait au document suivant, qui contient l'expression «cuire un aliment»:

"_id": ObjectId ("55f5289bb592880356441eab"), "sujet": "Les chats mangent les rats", "le contenu": "Les rats ne cuisinent pas les aliments", "aime": 55, "année": 2014, "langue": "anglais", "partition": 0.6666666666666666

Recherche de négation

Préfixer un mot clé de recherche avec - (signe moins) exclut tous les documents contenant le terme nié. Par exemple, essayez de rechercher tout document contenant le mot clé rat mais ne contient pas des oiseaux en utilisant la requête suivante:

db.messages.find ($ text: $ search: "rat -birds", score: $ meta: "textScore"). sort (score: $ meta: "textScore" )

Regarder dans les coulisses

Une fonctionnalité importante que je n’ai pas révélée jusqu’à présent est la façon dont vous regardez en coulisses et voyez comment vos mots-clés de recherche sont en cours de suppression, d’arrêter les libellés, d’annuler, etc.. $ expliquer à la rescousse. Vous pouvez exécuter la requête explicit en passant vrai en tant que paramètre, ce qui vous donnera des statistiques détaillées sur l'exécution de la requête.  

db.messages.find ($ text: $ search: "les chiens qui ne mangent pas les chats mangent des rats \" les chiens mangent \ "-friends", score: $ meta: "textScore"). sort ( score: $ meta: "textScore"). expliquer (vrai) 

Si vous regardez le queryPlanner objet renvoyé par la commande explicit, vous pourrez voir comment MongoDB a analysé la chaîne de recherche donnée. Observez qu'il a négligé les mots vides comme qui, et à tige chiens à chien

Vous pouvez également voir les termes que nous avons négligés de notre recherche et les expressions que nous avons utilisées dans le texte. parsedTextQuery section.  

"parsedTextQuery": "terms": ["chien", "chat", "ne pas", "mange", "mangé", "rat", "chien", "mange"], "niéTermes": ["ami "]," phrases ": [" les chiens mangent "]," niéPhrases ": [] 

La requête explicitée sera très utile car nous effectuons des requêtes de recherche plus complexes et voulons les analyser..

Recherche textuelle pondérée

Lorsque nous avons des index sur plusieurs champs de notre document, la plupart du temps, un champ sera plus important (c’est-à-dire plus important) que l’autre. Par exemple, lorsque vous effectuez une recherche sur un blog, son titre doit avoir le poids le plus élevé, suivi du contenu du blog..

La pondération par défaut pour chaque champ indexé est 1. Pour affecter des pondérations relatives aux champs indexés, vous pouvez inclure poids option en utilisant le createIndex commander.

Comprenons cela avec un exemple. Si vous essayez de chercher le cuisinier mot clé avec nos index actuels, il en résultera deux documents, qui ont tous deux le même score.   

db.messages.find ($ text: $ search: "cook", score: $ meta: "textScore"). sort (score: $ meta: "textScore")
"_id": ObjectId ("55f5289cb592880356441ead"), "sujet": "Les oiseaux peuvent cuisiner", "contenu": "Les oiseaux ne mangent pas de rats", "aime": 12, "année": 2013, "lieu": "Chicago", "langue": "anglais", "partition": 0.6666666666666666 "_id": ObjectId ("55f5289bb592880356441eab"), "sujet": "Les chats mangent les rats", "contenu": "Le rat ne cuisine pas "," aime ": 55," année ": 2014," langue ":" anglais "," partition ": 0.6666666666666666 

Modifions maintenant nos index pour inclure des poids. avec le assujettir champ ayant un poids de 3 contre le contenu champ ayant un poids de 1.

db.messages.createIndex ("$ **": "text", "poids": sujet: 3, contenu: 1)

Essayez de rechercher un mot clé cuisinier maintenant, et vous verrez que le document qui contient ce mot-clé dans le assujettir le champ a un score plus élevé (de 2) que l'autre (qui en a 0,66).

"_id": ObjectId ("55f5289cb592880356441ead"), "sujet": "Les oiseaux peuvent cuisiner", "contenu": "Les oiseaux ne mangent pas de rats", "aime": 12, "année": 2013, "lieu": "Chicago", "langue": "anglais", "partition": 2 "_id": ObjectId ("55f5289bb592880356441eab"), "sujet": "Les chats mangent les rats", "contenu": "Les rats ne cuisinent pas les aliments "," aime ": 55," année ": 2014," langue ":" anglais "," partition ": 0.6666666666666666 

Partitionnement d'index de texte

Au fur et à mesure que les données stockées dans votre application augmentent, la taille de vos index de texte continue à augmenter également. Avec cette augmentation de la taille des index de texte, MongoDB doit rechercher toutes les entrées indexées chaque fois qu'une recherche de texte est effectuée.. 

Pour que votre recherche de texte reste efficace avec des index croissants, vous pouvez limiter le nombre d'entrées d'index numérisées en utilisant des conditions d'égalité avec un indicateur standard. $ text chercher. Un exemple très courant de ceci serait la recherche de tous les articles publiés au cours d’une année / mois donnée, ou la recherche de tous les articles ayant une certaine catégorie / étiquette..

Si vous observez les documents sur lesquels nous travaillons, nous avons un année champ en eux que nous n'avons pas encore utilisé. Un scénario courant consisterait à rechercher des messages par année, en même temps que la recherche en texte intégral sur laquelle nous nous sommes renseignés.. 

Pour cela, nous pouvons créer un index composé qui spécifie une clé d’index ascendante / descendante sur année suivi d'un index de texte sur le assujettir champ. En faisant cela, nous faisons deux choses importantes:

  • Nous partitionnons logiquement toutes les données de la collection en ensembles séparés par année..
  • Cela limiterait la recherche de texte à n’analyser que les documents qui appartiennent à une année spécifique (ou l’appeler ensemble).

Supprimez les index que vous avez déjà et créez un nouvel index composé sur (année, assujettir):

db.messages.createIndex ("year": 1, "subject": "text")

Exécutez maintenant la requête suivante pour rechercher tous les messages créés en 2015 et contenant le chats mot-clé:

db.messages.find (year: 2015, $ text: $ search: "cats", score: $ meta: "textScore"). sort (score: $ meta: "textScore" )

La requête ne renverrait qu'un seul document correspondant, comme prévu. Si vous Explique cette requête et regardez le exécutionStats, vous trouverez ça totalDocsExamined 1 correspond à la requête, ce qui confirme que notre nouvel index a été utilisé correctement et MongoDB n'a dû numériser qu'un seul document tout en ignorant en toute sécurité tous les autres documents ne relevant pas de 2015..

Index de texte: Avantages

Que peuvent faire les index de texte??

Nous avons parcouru un long chemin dans cet article en apprenant sur les index de texte. Il existe de nombreux autres concepts que vous pouvez expérimenter avec les index de texte. Mais compte tenu de la portée de cet article, nous ne pourrons pas en discuter en détail aujourd'hui. Néanmoins, examinons brièvement ces fonctionnalités:

  • Les index de texte offrent un support multilingue, vous permettant de rechercher dans différentes langues à l'aide du $ langue opérateur. MongoDB prend actuellement en charge environ 15 langues, dont le français, l'allemand, le russe, etc..
  • Les index de texte peuvent être utilisés dans les requêtes de pipeline d'agrégation. L'étape de correspondance dans une recherche agrégée peut spécifier l'utilisation d'une requête de recherche de texte intégral.
  • Vous pouvez utiliser vos opérateurs habituels pour les projections, les filtres, les limites, les tris, etc., tout en travaillant avec des index de texte..

Index de texte MongoDB et bases de données de recherche externes

Gardant à l'esprit le fait que la recherche en texte intégral MongoDB ne remplace pas totalement les bases de données de moteurs de recherche traditionnelles utilisées avec MongoDB, il est recommandé d'utiliser la fonctionnalité native de MongoDB pour les raisons suivantes:

  • Selon une récente conférence à MongoDB, la portée actuelle de la recherche de texte fonctionne parfaitement pour la majorité des applications (environ 80%) créées avec MongoDB aujourd'hui..
  • La création des capacités de recherche de votre application dans la même base de données d’application réduit la complexité architecturale de l’application..
  • La recherche de texte MongoDB fonctionne en temps réel, sans retard ni mise à jour par lot. Au moment où vous insérez ou mettez à jour un document, les entrées d'index de texte sont mises à jour.
  • La recherche de texte étant intégrée aux fonctionnalités du noyau de base de données de MongoDB, elle est totalement cohérente et fonctionne bien, même avec le sharding et la réplication..
  • Il s'intègre parfaitement à vos fonctionnalités Mongo existantes telles que les filtres, l'agrégation, les mises à jour, etc..    

Index de texte: inconvénients

La recherche en texte intégral étant une fonctionnalité relativement nouvelle dans MongoDB, certaines fonctionnalités lui font actuellement défaut. Je les diviserais en trois catégories. Regardons.

Fonctionnalités manquantes à la recherche de texte

  • À l'heure actuelle, les index de texte ne sont pas en mesure de prendre en charge les interfaces enfichables telles que les stemmers, les mots vides, etc..
  • Actuellement, ils ne prennent pas en charge des fonctionnalités telles que la recherche basée sur des synonymes, des mots similaires, etc..
  • Ils ne stockent pas les positions des termes, c.-à-d. Le nombre de mots par lesquels les deux mots-clés sont séparés.
  • Vous ne pouvez pas spécifier l'ordre de tri d'une expression de tri à partir d'un index de texte..

Restrictions dans les fonctionnalités existantes

  • Un index de texte composé ne peut inclure aucun autre type d'index, comme des index à plusieurs clés ou des index géospatiaux. En outre, si votre index de texte composé inclut une clé d'index avant la clé d'index de texte, toutes les requêtes doivent spécifier les opérateurs d'égalité pour les clés précédentes..
  • Il existe certaines limitations spécifiques à la requête. Par exemple, une requête ne peut spécifier qu'un seul $ text expression, vous ne pouvez pas utiliser $ text avec $ ni, vous ne pouvez pas utiliser le allusion() commande avec $ text, en utilisant $ text avec $ ou a besoin de toutes les clauses de votre $ ou expression à indexer, etc..

Inconvénients de performance

  • Les index de texte créent une surcharge lors de l'insertion de nouveaux documents. Cela se répercute sur le débit d'insertion.
  • Certaines requêtes telles que les recherches d'expressions peuvent être relativement lentes.

Emballer 

La recherche en texte intégral a toujours été l’une des fonctionnalités les plus demandées de MongoDB. Dans cet article, nous avons commencé par une introduction à la recherche en texte intégral, avant de passer aux bases de la création d'index de texte.. 

Nous avons ensuite exploré l’indexation composée, l’indexation par caractères génériques, la recherche de phrases et la recherche de négations. En outre, nous avons exploré certains concepts importants tels que l'analyse des index de texte, la recherche pondérée et le partitionnement logique de vos index. Nous pouvons nous attendre à des mises à jour majeures de cette fonctionnalité dans les prochaines versions de MongoDB.. 

Je vous recommande d'essayer la recherche de texte et de partager vos impressions. Si vous l'avez déjà implémenté dans votre application, merci de partager votre expérience ici. Enfin, n'hésitez pas à poster vos questions, réflexions et suggestions sur cet article dans la section commentaires..