Space Vatican

Wenn Sie Daten indizieren, ist die Welt selten so einfach wie jedes isoliert vorhandene Dokument. Manchmal ist es besser, alle Daten in die untergeordneten Dokumente zu denormalisieren. Wenn Sie beispielsweise Bücher modellieren, kann das Hinzufügen eines Autorenfelds zu Büchern eine sinnvolle Wahl sein (auch wenn die Daten in der Datenbank, die Ihre maßgebliche Datenquelle ist, in separate authors – und books -Tabellen aufgeteilt sind). Es ist einfach und Sie können problemlos Abfragen für beide Attribute des Buches und den Namen des Autors erstellen.

Das ist nicht immer praktisch – das übergeordnete Dokument enthält möglicherweise zu viele Daten, um es in jedem untergeordneten Dokument zu duplizieren. Wenn Sie eine typische Blog- / Kommentar-App hätten, möchten Sie nicht den gesamten Inhalt des Blogposts in jedem Kommentar wiederholen, da dies die Anzahl der indizierten Daten erheblich erhöhen würde. Ohne dies können Sie jedoch nicht einfach Abfragen schreiben, um Kommentare zu Posts zu finden, die bestimmten Kriterien entsprechen (außer in 2 Schritten, in denen zuerst passende Posts gefunden und dann Kommentare mit einem bestimmten post_id abgerufen werden), was oft unhandlich oder langsam ist (oder beides)).

Eine andere Möglichkeit besteht darin, untergeordnete Dokumente innerhalb des übergeordneten Dokuments zu platzieren, z. B. können Sie Dokumente des Formulars haben

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

Ein Nachteil hierbei ist, dass das Hinzufügen eines untergeordneten Elements eine Neuindizierung des gesamten Dokuments erfordert. Autoren zu finden, die Bücher mit einem bestimmten Genre geschrieben haben, ist einfach, aber Autoren zu finden, die ein Science-Fiction-Buch von Penguin veröffentlicht haben, ist schwieriger.

Wenn unser Index

enthält, findet die offensichtlichste Abfrage

beide Autoren – Sie können nicht ausdrücken, dass Ihre Bedingungen auf published und genre müssen mit demselben Buch übereinstimmen.

ElasticSearch bietet zwei Dinge, die dabei helfen. Das erste ist das Konzept eines verschachtelten Dokuments / einer verschachtelten Abfrage. Auf diese Weise können Sie sagen, dass Sie nach Autoren suchen, bei denen mindestens ein Buch beide Kriterien erfüllt.

Zuerst müssen Sie eine Zuordnung einrichten, die besagt, dass das Feld books verschachtelt werden soll:

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

Wenn wir dann die gleichen Daten wie zuvor in diesen neuen Index einfügen, dann diese Abfrage

Hier können Sie mit dem Filter nested eine Abfrage für die verschachtelten Dokumente (dh die Bücher) ausführen und Autoren nach denen filtern, die mindestens ein verschachteltes Dokument haben, das der Abfrage entspricht. Die Option path gibt an, für welchen Teil des Autorendokuments diese Abfrage gilt, und dann ist die Option query eine Abfrage, die für diese verschachtelten Dokumente ausgeführt werden soll. Im Gegensatz zur vorherigen Abfrage muss ein einzelnes Buch gefunden werden, das beide Anforderungen erfüllt, sodass nur Alaistair Reynolds zurückgegeben wird

Parent & child

Das andere von elasticsearch bereitgestellte Konzept ist das einer Eltern-Kind-Beziehung zwischen Dokumenten. Das vorherige Beispiel kann mit Autoren als übergeordnete Dokumente und Büchern als untergeordnete Dokumente überarbeitet werden.

Indizieren Sie diesmal die Autoren getrennt von ihren Büchern:

Konfigurieren Sie dann die Zuordnung für den Buchtyp und geben Sie an, dass der übergeordnete Typ bare_author ist. Sie müssen dies tun, bevor Sie Bücher erstellen.

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

Wenn wir Bücher indizieren, müssen Sie dann die ID ihrer Eltern angeben (dh wir geben die ID eines der zuvor erstellten Autoren an)

Elasticsearch bietet einen has_child -Filter, der so ziemlich das tut, was auf der Dose steht: es werden übergeordnete Dokumente ausgewählt, bei denen mindestens ein untergeordnetes Dokument eine bestimmte Abfrage erfüllt. Diese Abfrage findet dann nur Alastair Reynolds:

Solr 4.0 wird anscheinend Joins ausführen können, obwohl dies meines Erachtens mit einigen Einschränkungen verbunden ist, insbesondere ohne Joins, wenn Sie in einer verteilten Umgebung betrieben werden. Indem elasticsearch sich auf übergeordnete / untergeordnete Typbeziehungen beschränkt, erleichtert es sich das Leben: Ein untergeordnetes Element wird immer im selben Shard wie sein übergeordnetes Element indiziert, sodass has_child keine umständlichen Cross-Shard-Operationen ausführen muss.

Listen erstellen

Sie können dies auch verwenden, um benutzerspezifische Listen gemeinsam genutzter globaler Elemente zu modellieren – z. B. wenn Sie Elemente möchten, die ein Benutzer bewertet hat. In diesem Fall würden Ihre untergeordneten Dokumente die Tatsache darstellen, dass ein bestimmter Benutzer einen bestimmten Beitrag bewertet hat – sie sind nichts anderes als eine user_id , post_id und eine Bewertung: eine Join-Tabelle in relationaler Datenbanksprache.

Durch die Verwendung einer Eltern-Kind-Beziehung und has_child können Sie alle von einem Benutzer bevorzugten Beiträge leicht finden, während Benutzer ihre Favoriten basierend auf dem Inhalt, dem Datum oder einem anderen Attribut eines Beitrags oder einer der untergeordneten Elementeigenschaften durchsuchen können. Das Hinzufügen eines Elements zur Liste der bewerteten Elemente ist kostengünstig – es muss nur ein sehr kleines rating Element indiziert werden.

Mit diesen Dokumenten

Diese Abfrage

findet nur „Bolivien bewertet 4“ da dies der einzige Beitrag ist, der Bolivien erwähnt und von dem Benutzer, an dem wir interessiert sind, über 3 bewertet wurde. Die Abfrage der obersten Ebene nach Titel gilt für die Beiträge, wobei die Abfrage innerhalb des Filters has_child Bedingungen beschreibt, denen die untergeordneten Elemente entsprechen müssen (in diesem Fall müssen sie einem bestimmten Benutzer angehören und mindestens eine bestimmte Bewertung haben).

Bestellen

Was has_child nicht zulässt, ist die Reihenfolge basierend auf Attributen der untergeordneten Elemente oder die Rückgabe von Attributen des untergeordneten Elements. Wenn Sie die bewerteten Beiträge eines Benutzers nach dem Zeitpunkt der Bewertung oder nach abnehmender Bewertung sortieren möchten, können Sie direkt nach Beiträgen / Bewertungen suchen, aber möglicherweise möchten Sie auch einige Suchkriterien auf die Beiträge anwenden. Zum Beispiel möchten Sie vielleicht nur bewertete Beiträge zu einem bestimmten Thema finden (immer noch nach der Bewertung, die der Benutzer gegeben hat). Mit has_child haben Sie kein Glück. Verschachtelte Dokumente helfen auch nicht.

Ab 0.19.10 können Sie den Filter has_parent verwenden. Dies funktioniert fast genauso wie has child , ermöglicht es Ihnen jedoch, stattdessen eine Abfrage für die übergeordneten Elemente anzugeben. Diese Abfrage gibt die Bewertungen von Benutzer 1234 für Beiträge zurück, deren Titel mit „Bolivien“ übereinstimmt, in absteigender Reihenfolge der Punktzahl

Dies gibt die Bewertungsobjekte zurück – Sie müssten dann die entsprechenden Beiträge mit einer separaten Abfrage abrufen.

Fälschen

Wenn Sie mit einer älteren Version von Elasticsearch nicht weiterkommen, können Sie den größten Teil des Weges mit top_children zurücklegen. Wie in der Dokumentation angegeben, fragt top_children zuerst die untergeordneten Dokumente ab und aggregiert sie dann zu übergeordneten Dokumenten. In unserem Beispiel bedeutet dies, dass elasticsearch zuerst die Bewertungsdokumente findet, die unserer Abfrage entsprechen. Dann wird jede Bewertung mit ihrem übergeordneten Beitrag abgeglichen und doppelte Beiträge zusammengefasst, sofern vorhanden.

Das Knifflige an Top-Kindern ist, dass Elasticsearch nicht im Voraus weiß, wie viele Dokumente bei der Aggregation verloren gehen. In diesem speziellen Fall ist es einfach, da zwei verschiedene Bewertungen desselben Benutzers immer zwei verschiedenen Beiträgen entsprechen, sodass wir uns nicht um die Einstellungen factor und incremental_factor kümmern müssen, da die Aggregationsphase niemals etwas bewirkt. Ebenso spielt der Score-Modus keine Rolle. Wenn Sie eine genaue Zählung der Gesamtzahl der Ergebnisse bereitstellen müssen, müssen Sie nur factor so groß festlegen, dass der erste Sweep, den elasticsearch von den untergeordneten Dokumenten ausführt, alle findet. Wenn Sie wissen, dass der Benutzer 500 bewertete Elemente auf seiner Liste hat und Sie nach den ersten 10 Elementen fragen, sollte ein Faktor von 50 ausreichen. Der Faktor muss nur eine Obergrenze sein – Sie müssen nicht genau wissen, wie viele Elemente der Benutzer auf seiner Liste hat (was ohne eine separate elastische Suchabfrage umständlich sein kann, wenn der Benutzer eine bestimmte Teilmenge seiner Bewertungen durchsucht).

Was Sie nach all dem erhalten, ist eine Liste der übergeordneten Dokumente (Beiträge), sortiert nach der Abfragebewertung der untergeordneten Dokumente (Bewertungen). Um das ursprüngliche Ziel zu erreichen, die Beiträge basierend auf Attributen der untergeordneten Dokumente zu sortieren, müssen wir nur sicherstellen, dass diese Abfragebewertung den richtigen Wert hat. Lassen Sie beispielsweise eine top_children -Abfrage eine custom_score -Abfrage umbrechen, damit Sie steuern können, wie hoch die Punktzahl für jedes Kind ist.

Mit denselben Dokumenten im Index gibt diese Abfrage die Beiträge zurück, die Benutzer 1234 bewertet hat, sortiert nach ihrer Bewertung:

Wir führen eine top_children -Abfrage aus, also müssen wir zuerst sagen, was der Typ der Kinder ist, die wir in Betracht ziehen (Bewertung). Dann stellen wir die Abfrage bereit, die diese Kinder findet. Dies ist eine custom_score -Abfrage, die eine filtered -Abfrage umschließt. Die filtered -Abfrage stellt sicher, dass wir nur Bewertungen des Benutzers finden, an dem wir interessiert sind, und dann lässt das script -Element die Punktzahl des Bewertungsdokuments selbst bewerten, sodass wir unsere Beiträge nach Bewertung sortieren. Die Funkiness mit den Backslashes ist nur, weil ich versuche, ein wörtliches einfaches Anführungszeichen in eine Shell-freundliche Zeichenfolge einzuschließen, die durch einfache Anführungszeichen begrenzt ist – der tatsächliche JSON, den wir senden, hat nur "script": "doc.value" .

Ruby fun

Leider unterstützt die Tire-Bibliothek im Moment nichts von diesem lustigen Zeug – es gibt ein kleines Moratorium für diese Art von Funktion, da im Moment jeder einzelne kleine Abfragetyp und jede einzelne Option hinzugefügt werden endet als separate Methoden, die über tire verstreut sind, was dem Betreuer verständlicherweise nicht gefällt. Sie können es jedoch irgendwie hacken.

Tire erlaubt es Ihnen nicht, die übergeordnete ID eines Dokuments beim Indizieren festzulegen. Dies ist einfach hinzuzufügen und wird nur durch das oben erwähnte Moratorium aufgehalten. Meine Gabel fügt diese Fähigkeit hinzu. Damit enden Sie mit

Das nächste Unglück ist, dass die automatische Indexerstellung von tire einen Typ pro Index voraussetzt, aber damit eine Eltern-Kind-Beziehung existiert, müssen sich beide Typen im selben Index befinden. Am Ende habe ich so etwas gemacht, um meine Indizes zu erstellen.

was nicht ganz so schön ist, aber die Arbeit erledigt.

Zuletzt müssen Sie die Abfrage tatsächlich durchführen. In Ermangelung von top_children , das tatsächlich Teil der API von tire ist, können Sie es so

Diese kleine Unannehmlichkeit baut die Abfrage als Hash auf und schiebt sie dann in tire, während sie wegschaut. Natürlich können Sie es so strukturieren, dass es einfach ist, andere Bedingungen (ob beim Posten oder bei der Bewertung) zur Suche hinzuzufügen. Sie können den JSON auch manuell erstellen und Post.search :payload => my_json (es gibt einen Fehler mit der Payload-Option, der mit der Logger-Erweiterung von tire-contrib kollidiert)

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.