Architecture de données optimale pour le marquage, les nuages, et la recherche (comme le flux de StackOverflow)?

j'aimerais savoir comment le balisage et la recherche de Stack Overflow sont architecturés, parce qu'il semble fonctionner assez bien.

Ce qui est une bonne base de données/modèle de recherche si je veux faire tout ce qui suit:

  1. stocker des étiquettes sur diverses entités, (comment normalisé? i.e. Entity, Tag, et Entity_Tag tables?)
    • recherche d'articles portant des étiquettes particulières
    • construire un nuage d'étiquettes de toutes les étiquettes qui s'appliquent à un ensemble de résultats de recherche
    • Comment afficher une liste d'étiquettes pour chaque élément dans un résultat de recherche?

peut-être est-il judicieux de stocker les étiquettes sous une forme normalisée, mais aussi sous forme de chaîne délimitée par l'espace aux fins des numéros 2, 4 et peut-être 3. Pensées?

j'ai entendu dire que Stack Overflow utilise Lucene pour la recherche. Est-ce vrai? J'ai entendu un deux podcasts sur L'optimisation SQL, mais rien sur Lucene. S'ils utilisent Lucene, je me demande quelle part du résultat de la recherche vient de Lucene, et si le nuage d'étiquettes "drill-down" vient de Lucene.

38
demandé sur rene 2008-10-24 22:38:33

2 réponses

Wow j'ai juste écrit un gros post et donc étouffé et accroché dessus, et quand j'ai appuyé sur mon bouton arrière pour soumettre à nouveau, l'éditeur de markup était vide. aaargh.

alors je recommence...

en ce qui concerne le débordement de la pile, il s'avère qu'ils utilisent SQL server 2005 full text search .

concernant les projets OS recommandés par @Grant:

  • *DotNetKicks utilise le DB pour le marquage et Lucene pour la recherche en texte intégral. Il semble qu'il n'y ait aucun moyen de combiner une recherche en texte intégral avec une recherche par étiquette
  • Kigg utilise Linq-to-SQL pour les requêtes de recherche et d'étiquette. Les deux requêtes rejoignent Stories->StoryTags - > Tags.
  • les deux projets ont une approche à trois tables pour le marquage comme tout le monde semble généralement recommander

j'ai aussi trouvé d'autres questions sur Alors que j'avais manqué avant:

Ce que je suis en train de faire, pour chacun des points que j'ai mentionné:

  1. dans le DB, 3 tableaux: Entity, Tag, Entity_Tag. J'utilise le DB pour:
    • Construire à l'échelle du site des nuages de tags
    • parcourir par balise (i.e. urls like SO's /questions/tagged/ASP.NET )
  2. Pour la recherche-je utiliser Lucene + NHibernate.Rechercher
    • les étiquettes sont concat'D dans un TagString qui est indexé par Lucene
      • donc j'ai la pleine puissance du moteur D'interrogation de Lucene (et / ou / pas queries)
      • je peux rechercher des textes et filtrer par tags en même temps
      • l'analyseur de Lucene fusionne les mots pour de meilleures recherches d'étiquettes (c.-à-d. qu'une recherche d'étiquettes pour "test" trouvera aussi des mots marqués "testing")
    • Lucene renvoie un jeu de résultats potentiellement énorme, que je pagine à 20 Résultats
    • alors NHibernate charge les entités de résultat par Id, soit de la DB ou du cache Entity
    • il est donc tout à fait possible qu'une recherche aboutisse à 0 hits au DB
  3. Je ne le fais pas encore, mais je pense que je vais probablement essayer de trouver un moyen de construire le nuage d'étiquette à partir de la corde D'étiquette à Lucene, plutôt que de prendre un autre DB hit
  4. N'ont pas encore fait cela non plus, mais je vais probablement stocker le TagString dans le DB de sorte que je puisse montrer la liste D'étiquette d'une entité sans avoir pour faire 2 jointures de plus.

cela signifie que chaque fois que les balises D'une entité sont modifiées, je dois:

  • insérer toute nouvelle étiquette qui n'existe pas déjà
  • Insérer / Supprimer de la table EntityTag
  • Mise À Jour De L'Entité.TagString
  • mise à Jour de l'index Lucene pour l'Entité

étant donné que le rapport entre la lecture et l'écriture est très grand dans mon application, je pense que je suis ok avec ça. La seule partie qui prend vraiment beaucoup de temps est L'indexation Lucene, parce que Lucene ne peut que insérer et supprimer de son index, donc je dois ré-indexer l'entity entière afin de mettre à jour le TagString. Je ne suis pas excitée à ce sujet, mais je pense que si je le fais dans un fil de fond, il sera très bien.

le temps nous le dira...

57
répondu Winston Fassett 2017-05-23 12:09:05

Je ne sais pas s'ils se qualifient comme optimaux, mais DotNetKicks et Kigg sont tous deux des implémentations de clones digg open source. Vous pouvez regarder comment ils font les étiquettes et la recherche.

mes meilleures suppositions sans beaucoup de délibération :)

  1. Je n'ai jamais aimé l'idée de sérialiser des valeurs multiples dans un seul champ, donc les chaînes délimitées stockées dans un seul champ ne m'intéressent pas... pourrait fonctionner pour les chemins adjacents avec des arbres, mais ceux-ci sont toujours commandé et les étiquettes n'ont pas besoin d'être. Cela semble comme il faudrait taxer le genre de travail d'opérateur que vous pourriez faire pour les trouver.

donc ma prise initiale est probablement Entity -> EntityTag <- Tag.

  1. cette approche rend la recherche d'articles via Tag assez facile, joindre en arrière par EntityTag, appelez-le un jour.

  2. vous avez besoin d'une opération secondaire ici pour sélectionner les étiquettes distinctes pour le jeu de résultats. Si A.) tirer le jeu de résultats, B.) normaliser l'espace de l'étiquette. Je pense que vous faites cela peu importe ce que la réponse est à #1 -- même le fait de coller des étiquettes dans un champ donnera toujours des étiquettes dupliquées (et vous devez les desérialiser pour effectuer cette opération--donc plus de travail, un autre argument pour une approche entièrement relationnelle).

  3. toujours facile. Voici un domaine où l'approche sérialisée fonctionne mieux. Pas besoin de se joindre pour les étiquettes d'enfant, il est juste là dans L'entité. Cela dit, tirant 0..n tags via les deux jointure de table ne semble pas trop difficile pour moi. Si vous parlez de considérations perf, construisez-le normalisé d'abord puis optimisez via cache ou denorm.

l'autre option est "faire les deux". Cela ressemble à une optimisation prématurée, mais vous pourriez faire l'approche complètement normalisée pour soutenir toutes les opérations centrées sur les balises et sérialiser sur persist pour avoir une version dénormalisée juste là dans l'entité. Un peu plus de travail, certains possibilité de sortir de la synchronisation si elle n'est pas entièrement couverte, mais le meilleur des deux mondes s'il y a des limites réelles à la façon complètement normalisée dans vos cas d'utilisation.

Lucene est intéressant aussi, vous pouvez déclarer des métadonnées spécifiques dans les indices IIRC, donc vous pouvez potentiellement tirer parti de la recherche de balises de cette façon aussi bien. Mon soupçon est que si vous allez trop loin dans cette voie, alors vous finissez par avoir des déconnexions entre ce que vous stockez dans la base de données et l'index à un moment donné. Je peux parler pour Lucene, C'est très efficace et facile à utiliser, je crois .Texte utilisé pour ses capacités de recherche et il a pris en charge tous les weblogs.asp.net avant de passer au serveur communautaire. Je m'en tiendrais à elle pour la recherche de texte complet si MSSQL n'est pas dans l'image/suffisant, résoudre les problèmes d'étiquette dans la base de données imo.

5
répondu Grant 2008-10-25 14:27:42