Espace Vatican

Lorsque vous indexez des données, le monde est rarement aussi simple que chaque document existant isolément. Parfois, il vaut mieux dénormaliser toutes les données dans les documents enfants. Par exemple, si vous modélisiez des livres, l’ajout d’un champ auteur aux livres pourrait être un choix judicieux (même si dans la base de données qui est votre source de données faisant autorité, les données sont divisées en tables distinctes authors et books). C’est simple et vous pouvez facilement construire des requêtes sur les attributs du livre et le nom de l’auteur.

Ce n’est pas toujours pratique – il peut y avoir trop de données dans le document parent pour les dupliquer dans chaque document enfant. Si vous aviez votre application de blog / commentaire typique, vous ne voudriez pas répéter tout le contenu de l’article de blog dans chaque commentaire car cela augmenterait considérablement la quantité de données indexées plusieurs. Pourtant, sans cela, vous ne pouvez pas facilement écrire des requêtes pour trouver des commentaires sur des publications correspondant à certains critères (autrement qu’en effectuant une étape 2 de processus consistant à d’abord trouver des publications correspondantes, puis à récupérer des commentaires avec un certain post_id, ce qui est souvent lourd ou lent (ou les deux)).

Une autre option consiste à placer des documents enfants dans le document parent, par exemple, vous pouvez avoir des documents du formulaire

123456789101112
{ "name": "A. N Author", "biography": "A leading wordsmith", "books": }

Un inconvénient ici est que l’ajout d’un enfant nécessite une réindexation de l’ensemble du document. Trouver des auteurs qui ont écrit des livres avec un certain genre est facile, mais trouver des auteurs qui ont publié un livre de science-fiction par penguin est plus difficile.

Si notre index contient

alors la requête la plus évidente

trouve les deux auteurs – vous ne pouvez pas exprimer que vos conditions sur published et genre doivent correspondre au même livre.

ElasticSearch fournit deux éléments qui aident à cela. Le premier est le concept d’un document / requête imbriqué. Cela vous permet de dire que vous recherchez des auteurs où au moins un livre répond à vos deux critères.

Vous devez d’abord configurer un mappage indiquant que le champ livres va être imbriqué:

123456789
curl -XPOST localhost:9200/authors/nested_author/_mapping -d '{ "nested_author":{ "properties":{ "books": { "type": "nested" } } }}'

Si nous insérons ensuite les mêmes données qu’auparavant dans ce nouvel index, cette requête

Ici le filtre nested vous permet d’exécuter une requête sur les documents imbriqués (c’est-à-dire les livres) et de filtrer les auteurs par ceux qui ont au moins un document imbriqué correspondant à la requête. L’option path nous indique à quelle partie du document d’auteur cette requête s’applique, puis l’option query est une requête à exécuter sur ces documents imbriqués. Contrairement à la requête précédente, cela nécessite qu’un livre individuel soit trouvé satisfaisant aux deux exigences, de sorte que seul Alaistair Reynolds est renvoyé

Parent & enfant

L’autre concept proposé par elasticsearch est celui d’une relation parent-enfant entre les documents. L’exemple précédent peut être retravaillé avec les auteurs en tant que documents parents et les livres en tant que documents enfants.

Cette fois, indexez les auteurs séparément de leurs livres:

Puis configurez le mappage pour le type de livre et dites que son type parent est bare_author. Vous devez le faire avant de créer des livres.

12345
curl -XPOST localhost:9200/authors/book/_mapping -d '{ "book":{ "_parent": {"type": "bare_author"} }}'

Lorsque nous indexons des livres, vous devez ensuite donner l’id de leur parent (c’est-à-dire que nous fournissons l’id de l’un des auteurs précédemment créés)

Elasticsearch fournit un filtre has_child qui fait à peu près ce qui est dit sur le tin: il sélectionne des documents parents avec au moins un enfant satisfaisant une certaine requête. Cette requête ne trouve alors qu’Alastair Reynolds:

Solr 4.0 aura apparemment la possibilité de faire des jointures, bien que pour autant que je sache, cela comporte certaines restrictions, en particulier aucune jointure si vous êtes exploité dans un environnement distribué. En se limitant aux relations de type parent / enfant, elasticsearch se facilite la vie: un enfant est toujours indexé dans le même fragment que son parent, de sorte que has_child n’a pas à effectuer d’opérations de fragment croisé gênantes.

Listes de création

Vous pouvez également l’utiliser pour modéliser des listes d’éléments globaux partagés spécifiques à l’utilisateur, par exemple si vous vouliez des éléments évalués par un utilisateur. Dans ce cas, vos documents enfants représenteraient le fait qu’un utilisateur spécifique avait évalué un post spécifique – ils ne sont rien de plus qu’un user_id, post_id et une note : une table de jointure dans le jargon de la base de données relationnelle.

L’utilisation d’une relation parent/enfant et has_child vous permet de trouver facilement toutes les publications favorisées par un utilisateur tout en permettant aux utilisateurs de rechercher dans leurs favoris en fonction du contenu de la publication, de la date ou de tout autre attribut d’une publication ou de l’une des propriétés de l’élément enfant. L’ajout d’un élément à la liste des éléments notés est bon marché – il ne nécessite que l’indexation d’un très petit élément rating.

Avec ces documents

Cette requête

ne trouve que « bolivie notée 4 » car c’est le seul article mentionnant la bolivie qui a été notée plus de 3 par l’utilisateur qui nous intéresse. La requête de niveau supérieur sur le titre s’applique aux publications, où la requête à l’intérieur du filtre has_child décrit les conditions que les enfants doivent respecter (dans ce cas, ils doivent appartenir à un utilisateur spécifique et avoir au moins une certaine note).

Commande

Ce que has_child ne vous permet pas de faire est de commander en fonction des attributs des enfants ou des attributs de retour de l’enfant. Si vous souhaitez commander les publications notées d’un utilisateur en fonction du moment où elles ont été notées ou en diminuant la note, vous pouvez rechercher directement les publications / notes, mais vous voudrez peut-être également appliquer des critères de recherche aux publications. Par exemple, vous voudrez peut-être ne trouver que des publications notées sur un certain sujet (toujours en fonction de la note donnée par l’utilisateur). Avec has_child, vous n’avez pas de chance. Les documents imbriqués n’aident pas non plus.

À partir du 0.19.10, vous pouvez utiliser le filtre has_parent. Cela fonctionne presque exactement de la même manière que son enfant, mais vous permet de spécifier une requête par rapport aux éléments parents à la place. Cette requête renvoie les évaluations par l’utilisateur 1234, sur les publications dont le titre correspond à « bolivie », dans un ordre de score décroissant

Cela renvoie les objets de notation – vous devrez ensuite récupérer les publications correspondantes avec une requête distincte.

Simuler

Si vous êtes bloqué sur une ancienne version d’elasticsearch, vous pouvez obtenir la plupart du chemin avec top_children. Comme le dit la documentation top_children interroge d’abord les documents enfants, puis les agrège en documents parents. Dans notre exemple, cela signifie qu’elasticsearch trouvera d’abord les documents de notation qui correspondent à notre requête. Ensuite, il fera correspondre chaque note à son message parent, en agrégeant les messages en double là où ils existent.

Le problème avec les meilleurs enfants est qu’elasticsearch ne sait pas à l’avance combien de documents il perdra lorsque l’agrégation aura lieu. Dans ce cas particulier, c’est facile car deux évaluations distinctes par le même utilisateur correspondent toujours à deux publications distinctes, nous n’avons donc pas besoin de nous soucier des paramètres factor et incremental_factor car la phase d’agrégation ne fait jamais rien. De même, le mode score n’a pas d’importance non plus. Si vous devez fournir un décompte précis du nombre total de résultats, il vous suffit de définir factor suffisamment grand pour que le premier balayage effectué par elasticsearch des documents enfants les trouve tous. Si vous savez que l’utilisateur a 500 éléments notés sur sa liste et que vous demandez les 10 premiers éléments, un facteur de 50 devrait faire l’affaire. Le facteur doit uniquement être une limite supérieure – vous n’avez pas besoin de savoir exactement combien d’éléments l’utilisateur a sur sa liste (ce qui peut être difficile à résoudre sans une requête de recherche élastique distincte si l’utilisateur recherche un sous-ensemble spécifique de ses évaluations).

Ce que vous obtenez après tout, c’est une liste de documents parents (publications) triés par le score de requête des documents enfants (évaluations). Pour atteindre l’objectif initial de trier les publications en fonction des attributs des documents enfants, il nous suffit de nous assurer que ce score de requête a la bonne valeur. Par exemple, demandez à la requête top_children d’envelopper une requête custom_score afin de pouvoir contrôler le score de chaque enfant.

Avec les mêmes documents dans l’index, cette requête renvoie les publications que l’utilisateur 1234 a notées, classées par leur note:

Nous exécutons une requête top_children, donc la première chose que nous devons faire est de dire quel est le type des enfants que nous considérons (évaluation). Ensuite, nous fournissons la requête qui trouve ces enfants. Il s’agit d’une requête custom_score, enveloppant une requête filtered. La requête filtered garantit que nous ne trouvons que les évaluations données par l’utilisateur qui nous intéresse, puis l’élément script fait en sorte que le score du document de notation soit évalué lui-même, de sorte que nous obtenons nos publications triées par évaluation. Le funkiness avec les barres obliques inverses est uniquement parce que j’essaie d’inclure un guillemet simple littéral dans une chaîne conviviale de shell délimitée par des guillemets simples – le json réel que nous envoyons n’a que "script": "doc.value".

Ruby fun

Malheureusement, la bibliothèque de pneus ne supporte pas vraiment ces choses amusantes pour le moment – il y a un peu de moratoire sur ce genre de fonctionnalité ajoutée car pour le moment, chaque petit type de requête et chaque option finissent par être des méthodes distinctes dispersées dans tout le pneu, ce que le mainteneur n’aime naturellement pas. Vous pouvez en quelque sorte le pirater.

Tire ne vous permet pas de définir l’id parent d’un document lors de l’indexation. Cela est simple à ajouter et n’est freiné que par le moratoire susmentionné. Ma fourchette ajoute cette capacité. Avec cela, vous vous retrouvez avec

Le prochain malheur est que la création d’index automatique de tire suppose un type par index, mais pour qu’une relation parent / enfant existe, les deux types doivent être dans le même index. J’ai fini par faire quelque chose comme ça pour créer mes index.

ce qui n’est pas aussi beau mais fait le travail.

Enfin, vous devez réellement faire la requête. En l’absence de top_children faisant réellement partie de l’api de tire, vous pouvez la fondre comme si

Ce petit peu de désagrément construit la requête sous forme de hachage, puis la pousse dans tire pendant qu’elle regarde dans l’autre sens. De toute évidence, vous pouvez le structurer de manière à ce qu’il soit facile d’ajouter d’autres conditions (que ce soit sur la publication ou sur la notation) à la recherche. Vous pouvez également construire le json manuellement et utiliser Post.search :payload => my_json (il y a un bogue avec l’option de charge utile qui se heurte à l’extension logger de tire-contrib)

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.