Changements radicaux pour la recherche sur la ZEP-12

Le problème exposé dans ce sujet a été résolu.

Salut à tous,

J'ai besoin de votre avis car Artragis, Pierre et moi sont confronté à un souci pour l'indexation des données pour la ZEP-12.

Le souci

Haystack ne semble pas être adaptés à nos nouveaux besoins. Grâce à la ZEP 12, on ne stocke plus les parties, chapitres et extraits dans la base de données. Avant on stockait, les parties, chapitres et extraits, plus quelques infos.

Le nouveau besoin est donc d'indexer du contenu, qui n'est pas dans la base de données.

Si on regarde la documentation, dans la partie FAQ, on trouve ceci,

When should I not be using Haystack?

Non-Model-based data. If you just want to index random data (flat files, alternate sources, etc.), Haystack isn’t a good solution. Haystack is very Model-based and doesn’t work well outside of that use case.

C'est effectivement impossible, d'indexer du contenu qui n'est pas dans la base de données, car lors de l'indexation, il faut lui donner un QuerySet (la méthode index_queryset). De plus, le modèle doit forcément étendre Model.model de Django. Lors du renvois des résultats de la recherche, Haystack va chercher dans les modèles enregistré. On peut lui préciser d'autre modèle mais ils doivent étendre Model.model de Django.

Pour plus d'information, sur le pourquoi, je vous renvois vers l'issue, ou j'explique le pourquoi avec le code correspondant.

Les solutions

  • Réindexer tous le contenu dans la base de données et faire comme avant.

    • Avantage:
      • "c'est comme avant".
      • On peut rediriger l'utilisateur jusqu'au extrait.
    • L'inconvénients:
      • c'est qu'on ré-indexe tout, uniquement pour la recherche. Ça "casse" le travail fait par Artragis et Pierre.
  • Stocker dans la base de donnée uniquement les extraits. L'idée, serait d'indexer le tuto avec toutes les informations dedans (l'extrait, chapitre, partie). On lance une recherche sur ce contenu en excluant les extraits. Quand on a trouvé quels tutos l'intéresserait. L'utilisateur clique sur un lien, ou on lui propose plusieurs extraits, chapitres, parties qui correspondent à ça recherche.

    • Avantage:
      • On indexe uniquement les extraits.
      • On peut rediriger l'utilisateur jusqu'au extrait.
    • Inconvénients:
      • Lors de la publication, pandoc créé l'équivalent du chapitre en HTML et on n'a que le chapitre lors de l'indexation, il faut donc récupérer le chapitre en HTML et tenter de lé découper en petite partie, c'est pas du tout évident vu comment sont généré les chapitres en html (pas de délimitation précise entre les extraits).
      • On indexe les extraits
      • Quand l'utilisateur, clique sur le lien pour afficher les extraits qui correspondent, il faudrait aussi rechercher sur les introductions, conclusions des chapitres, ainsi que les introductions et chapitres et chapitres des parties, à la main. Cela génère donc 2xle nombre de chapitres + 2 x le nombres de parties + 2 (introduction et conclusion du tutoriel) d'IO pour aller lire les contenus sur le disque. De plus, on sera jamais aussi performant en recherchant à la main, qu'en recherchant avec Haystack.
  • Stocker dans la base de donnée uniquement les chapitres (Poc dispo). L'idée serait la même que pour la solution du-dessus.On n'indexe le tuto avec toutes les informations dedans (l'extrait, chapitre, partie). On lance une recherche sur ce contenu en excluant les chapitres. Quand on a trouvé quels tutos l'intéresserait. L'utilisateur clique sur un lien, ou on lui propose plusieurs extraits, chapitres, parties qui correspondent à ça recherche.

    • Avantage:
      • On indexe uniquement les chapitres.
    • Inconvénients:
      • On redirige uniquement vers les chapitres et non plus vers les extraits. C'est suffisant.
      • On indexe les chapitres
      • Quand l'utilisateur, clique sur le lien pour afficher les chapitres qui correspondent, il faudrait aussi rechercher sur les introductions, conclusions des parties, ainsi que les introductions et conclusion du tutoriel, à la main. Cela génère donc 2 x le nombres de parties + 2 (introduction et conclusion du tutoriel) d'IO pour aller lire les contenus sur le disque. C'est pas non plus énorme, les plus gros tutoriels font 5 parties et donc génère 12 IO et c'est uniquement quand on recherche un big-tuto et que l'utilisateur clique sur le lien.

Les captures de cette solution, en live:

Quand un utilisateur recherche le terme "Android" dans la bare de recherche, il tombe sur cette interface: ZEP-12.

Notez le nouveau lien Indications de la recherche. Si l'utilisateur clique sur le lien, le systéme lui affiche: Recherche ZEP-12.

On voit tous les résultats qui correspondent à ça recherche dans le tutos. Il peut être rediriger sur le chapitre (et non sur l'extrait comme aujourd'hui).

Que faire ?

Quels solutions est dans votre coeur ? Avez-vous une autre solution ? Qu'en pensez-vous ? Une autre lib, qui correspond mieux au besoin ?

PS: Désolé pour le post un peu long … .

+0 -0

Est-ce qu'on ne pourrait pas imaginer de stocker les contenus dans une table spéciale qui ne servirait qu'à l'indexation ?

J'ai ça sur un projet, ça marche pas mal :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Données "brutes" (ici nos fichiers .md ou .html sur le disque)
    |
  (pré-traitement)
    |
    V
Données prêtes à être indexées, dans des tables spéciales
    |
  (indexation)
    |
    V
Index Solr

Les tables de la base intermédiaire peuvent même être dans une base séparée si ça nous simplifie la vie.

Le pré-traitement est fait à la publication, et idéalement peut être fait à la demande sur un tuto arbitraire.

Les tables spéciales intermédiaires contiennent :

  • Toutes les données à indexer - l'indexation se contente de lire ces tables sans faire la moindre transformation
  • Ces tables sont donc complètement dé-normalisées : si une information est présente à N endroits, elle est dupliquée N fois
  • Toutes les informations pour récupérer l'extrait précis à afficher avec le minimum d'I/O

Avantages

  • Solution la plus efficace en lecture
  • Permet de simplifier au maximum l'indexation elle-même
  • On a l'ergonomie de la solution 1 sans pour autant casser le travail de performances fait sur la ZEP-12

Inconvénients

  • Besoin d'une étape supplémentaire dans la publication d'un tuto
  • Un peu plus consommateur en taille de base de données

PS : Contrairement à mon voisin du dessus, je trouve le POC incompréhensible d'un point de vue ergonomique : pourquoi quand je fais une recherche je ne vois pas tous mes résultats directement ? Personne ne fonctionne ainsi.

Est-ce qu'on ne pourrait pas imaginer de stocker les contenus dans une table spéciale qui ne servirait qu'à l'indexation ?

C'est en gros, ce que je fais aujourd'hui. J'ai créé une table à part "SearchIndexChapter" qui contient les différents chapitres. Je la remplis à l'indexation, et j'y touche plus après. Mais moi, j'ai toujours ce lien avec le PublishableContent. C'est pas vraiment la même solution, même si ça se rapproche.

Je l'ai pas faite pour toute, pour une simple raison, c'est que la solution que tu as donné pour moi, à un autre désavantage que tu n'as pas cité. Lors de la publication, le moment ou tu valide ton tutoriel, tu dois comparer ce que tu avait avant et le nouveau contenu. La comparaison va être quand même compliquée à faire. Y'a la méthode, "bourin" qui est de dire, on supprime tous ce qui concerne le tuto, et on le remet dans la table. Mais cette solution à un coup pour moi, soit de dev ou soit de performance. Imagine un peu pour un big-tuto comme celui d'android, ou tu as un 92 extraits soit une centaine IO à faire, si tu veux mettre le contenu en base. Il faut ajouter les chapitres et parties après. L'enregistrement en base de données aussi. Tu pourrais me répondre, que de toute manière, il vaut mieux le faire à la publication qu'à chaque rebuild_index. Tu aurais probablement raison.

Attention: Je dis pas que la solution ne correspond pas mais que le coût est à connaitre, ce qui est très très différents.

J'ai ça sur un projet, ça marche pas mal :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Données "brutes" (ici nos fichiers .md ou .html sur le disque)
    |
  (pré-traitement)
    |
    V
Données prêtes à être indexées, dans des tables spéciales
    |
  (indexation)
    |
    V
Index Solr

Les tables de la base intermédiaire peuvent même être dans une base séparée si ça nous simplifie la vie.

Le pré-traitement est fait à la publication, et idéalement peut être fait à la demande sur un tuto arbitraire.

Cette contrainte sur le tuto arbitraire, est un peu plus compliqué à faire, je pense. En tous cas, elle nécessite réflexion.

Les tables spéciales intermédiaires contiennent :

  • Toutes les données à indexer - l'indexation se contente de lire ces tables sans faire la moindre transformation
  • Ces tables sont donc complètement dé-normalisées : si une information est présente à N endroits, elle est dupliquée N fois
  • Toutes les informations pour récupérer l'extrait précis à afficher avec le minimum d'I/O

Rien à redire.

Avantages

  • Solution la plus efficace en lecture

C'est très vrai.

  • Permet de simplifier au maximum l'indexation elle-même

Ouai, mais tu la complique lors de la publication ! ^^

  • On a l'ergonomie de la solution 1 sans pour autant casser le travail de performances fait sur la ZEP-12

Même remarque, c'est plus long lors de la publication. Mais bon, on a rien sans rien.

Inconvénients

  • Besoin d'une étape supplémentaire dans la publication d'un tuto
  • Un peu plus consommateur en taille de base de données

Ouai, c'est le coût à payer et rien n'est gratuit.

PS : Contrairement à mon voisin du dessus, je trouve le POC incompréhensible d'un point de vue ergonomique : pourquoi quand je fais une recherche je ne vois pas tous mes résultats directement ? Personne ne fonctionne ainsi.

SpaceFox

Tu as tous les résultats, car tu as le tutoriel. Tu as pas le détail ou ça à matcher, c'est tout. C'est une question de gout et de contexte. Avant quand tu avais le malheur de taper un mot clé qui était dans le titre du sujet, il te ramenaais tous les messages du sujet. Tu te retrouvais avec 15 pages de résultat avec le même résultat.

Edit: appuyé trop vite sur le bouton "Envoyer"

+1 -0
  • Besoin d'une étape supplémentaire dans la publication d'un tuto
  • Un peu plus consommateur en taille de base de données

déjà que je milite pour que pandoc ne soit plus fait à la publication mais après, si on commence ce jeu là, on n'aura pas fini. La publication c'est long et ça fait TOUJOURS planter un worker, y ajouter un truc…

Je ne peux pas dire quelle solution est la meilleure (ou s'il en existe d'autres) mais je rebondis là-dessus :

PS : Contrairement à mon voisin du dessus, je trouve le POC incompréhensible d'un point de vue ergonomique : pourquoi quand je fais une recherche je ne vois pas tous mes résultats directement ? Personne ne fonctionne ainsi.

et là-dessus :

Tu as tous les résultats, car tu as le tutoriel. Tu as pas le détail ou ça à matcher, c'est tout. C'est une question de gout et de contexte. Avant quand tu avais le malheur de taper un mot clé qui était dans le titre du sujet, il te ramenaais tous les messages du sujet. Tu te retrouvais avec 15 pages de résultat avec le même résultat.

D'une certaine façon vous avez tous les deux raison. C'est pas logique d'un point de vue utilisateur, mais c'est impraticable du point de vue développeur. J'ajoute qu'avoir 15 fois le même résultat c'est mauvais aussi pour l'utilisateur.

Alors, on peut imaginer un système hybride : une page qui affiche les résultats avec un message du type : "X occurrences ont été trouvées dans ce tutoriel". Et en dessous un lien qui permet d'afficher les X occurrences en question de façon dynamique (sur la même page). Parce que devoir changer de page 36 fois merci bien.

Et on fait de même pour le résultat suivant.

Le souci

Haystack ne semble pas être adaptés à nos nouveaux besoins. Grâce à la ZEP 12, on ne stocke plus les parties, chapitres et extraits dans la base de données. Avant on stockait, les parties, chapitres et extraits, plus quelques infos.

Le nouveau besoin est donc d'indexer du contenu, qui n'est pas dans la base de données.

Si on regarde la documentation, dans la partie FAQ, on trouve ceci,

When should I not be using Haystack?

Non-Model-based data. If you just want to index random data (flat files, alternate sources, etc.), Haystack isn’t a good solution. Haystack is very Model-based and doesn’t work well outside of that use case.

C'est effectivement impossible, d'indexer du contenu qui n'est pas dans la base de données, car lors de l'indexation, il faut lui donner un QuerySet (la méthode index_queryset). De plus, le modèle doit forcément étendre Model.model de Django. Lors du renvois des résultats de la recherche, Haystack va chercher dans les modèles enregistré. On peut lui préciser d'autre modèle mais ils doivent étendre Model.model de Django.

Hugo

Personnellement je ne comprend pas d’où vient le souci ici. Je récapitule et arretez moi si je dis une betise.

  • On veut indexer uniquement le contenu publiés
  • Les métadonnées du contenu publié sont stockés dans la base de donnée (cf. le modele de la zep12)
  • ça signifie donc que Haystack peut indexer les métadonnées

Le truc c'est que nous on veut indexer les données. Donc il suffit de rajouter une fonction dans la classe PublishedContent de la ZEP-12 qu'on appellera get_real_content(), puis on rétabli le mapping à travers ce fichier comme s'est fait actuellement pour {{ object.get_introduction_online }}.

Du coup, j'ai du mal à voir pourquoi ça serait plus complexe que ça.

"Une étape en plus dans la publication d'un tuto" ne veut pas dire que cette étape doit être synchrone et bloquante.

SpaceFox

Exact, mais comme ça jamais été utilisé et que j'ai aucune idée de comment on fait ! ^^ En plus, la gestion des threads est pas changé dans Python 3 ?

Alors, on peut imaginer un système hybride : une page qui affiche les résultats avec un message du type : "X occurrences ont été trouvées dans ce tutoriel". Et en dessous un lien qui permet d'afficher les X occurrences en question de façon dynamique (sur la même page). Parce que devoir changer de page 36 fois merci bien.

Le lien que tu parle, c'est le lien dans la capture "Indications de recherche". Quand tu clique, ça affiche direct les résultats sur la même page, sans rafraîchir. J'aurais bien voulu, vous montrer en demo, mais sur le serveur artagis, on ne peut pas publié de big-tuto.

@Firm1, tu es d'accord, que dans la base de donnée, on a que les PublishedContent ? On n'a donc qu'un seul modéle dans Haystack. Je dit pas que c'est pas possible d'indexer tous les extraits, chapitres et parties. Je le fait même aujourd'hui sur ma branche. Mais tu es obligé de le faire dans l'objet PublishedContent. Le souci est quand tu va récupérer ton objet, lors des résultats, Haystack va te renvoyer qu'un seul objet, le PublishedContent mais tu peux pas savoir, si c'est une partie, un chapitre ou un extrait qui est pertinent et encore moins lequel.

+2 -0

Exact, mais comme ça jamais été utilisé et que j'ai aucune idée de comment on fait ! ^^ En plus, la gestion des threads est pas changé dans Python 3 ?

asynchrone peut vouloir dire "on lance un tachecron" par exemple. Du coup la génération de l'index n'est pas synchrone à la publication.

C'est exactement ce que je voudrai faire pour la genération du PDF.

Je profite de cette digression pour rappeler qu'une méthode qui s'appelle get_quelquechose() n'a pas le droit de faire quoi que ce soit de plus compliqué qu'un calcul trivial.

Pourquoi ? Parce que du coup, le get_ sous-entends très fortement qu'on récupère une donnée portée par l'objet et donc qu'on peut utiliser la méthode sans risque de performances. Si ce n'est pas le cas, ça peut poser de vrais problèmes comme on en a déjà pas mal dans le code.

Par exemple, un meilleur nom pour get_real_content() serait quelque chose comme retrieve_real_content()

@Firm1, tu es d'accord, que dans la base de donnée, on a que les PublishedContent ? On n'a donc qu'un seul modéle dans Haystack. Je dit pas que c'est pas possible d'indexer tous les extraits, chapitres et parties. Je le fait même aujourd'hui sur ma branche. Mais tu es obligé de le faire dans l'objet PublishedContent. Le souci est quand tu va récupérer ton objet, lors des résultats, Haystack va te renvoyer qu'un seul objet, le PublishedContent mais tu peux pas savoir, si c'est une partie, un chapitre ou un extrait qui est pertinent et encore moins lequel.

Hugo

Sauf si tu as les méthodes adéquates dans ta classe.

Par exemple, avec une fonction get_contents() qui te renvoit le dictionnaire {"url": "http://", "title": "mon extrait", value: "Blabla"}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from haystack import indexes
from .models import PublishedContent


class ContentIndex(indexes.SearchIndex, indexes.Indexable):
    text = indexes.CharField(document=True)
    contents = indexes.MultiValueField(model_attr='get_contents')

    def get_model(self):
        return PublishedContent

Je suis désolé, je ne voit peut-être pas l'évidence qui est sous mon nez, mais je comprend pas, tu seras toujours bloqué quand tu recevra les résultat, tu auras que ton PublishedContent. Et tu ne saura jamais quel champs t'on permis de trouver ton résultat.

Ps: prouve le avec un POC. Si tu as 5 minutes.

+1 -0

j'ai tendance à penser qu'un indexes.MultiValueField se comporte comme une liste de dictionnaire? Du coup quand tu fais ton "search" tu dis "je cherche sur l'attribut +content+ du dico mais je veux que tu m'affiche l'attribut +url+"

ce n'est qu'une supposition.

+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte