Was ist ein „DisMax“?
Der Begriff „dismax“ wird in den Solr-Listen häufig verwendet, was für neue Benutzer ziemlich verwirrend sein kann. Ursprünglich war er…
Der Begriff „dismax“ wird in den Solr-Listen häufig verwendet, was für neue Benutzer ziemlich verwirrend sein kann. Ursprünglich war er ein Kurzname für den DisMaxRequestHandler (den ich nach dem DisjunctionMaxQueryParser benannt habe, den ich nach der Klasse DisjunctionMaxQuery benannt habe, die er stark nutzt). In den letzten Jahren wurden der DisMaxRequestHandler und der StandardRequestHandler in eine einzige SearchHandler-Klasse umgewandelt, und jetzt bezieht sich der Begriff „dismax“ normalerweise auf den DisMaxQParser.
Klar wie Kloßbrühe, oder?
Unabhängig davon, ob Sie den DisMaxRequestHandler über den Parameter qt=dismax
oder den SearchHandler mit dem DisMaxQParser über defType=dismax
verwenden, ist das Endergebnis, dass Ihr Parameter q
vom DisjunctionMaxQueryParser geparst wird.
Die ursprünglichen Ziele von dismax (welche Bedeutung Sie auch immer daraus ableiten mögen) haben sich nie geändert:
… unterstützt eine vereinfachte Version der Syntax des Lucene QueryParsers. Anführungszeichen können verwendet werden, um Phrasen zu gruppieren, und +/- können verwendet werden, um obligatorische und optionale Klauseln zu kennzeichnen … aber alle anderen Lucene QueryParser-Sonderzeichen werden zur Vereinfachung der Benutzererfahrung escaped. Der Handler übernimmt die Verantwortung für den Aufbau einer guten Abfrage aus der Benutzereingabe unter Verwendung von BooleanQueries mit DisjunctionMaxQueries über Felder und Boosts, die Sie angeben. Außerdem können Sie zusätzliche Boosting-Abfragen, Boosting-Funktionen und Filterabfragen bereitstellen, um das Ergebnis aller Suchen künstlich zu beeinflussen. Diese Optionen können alle als Standardparameter für den Handler in Ihrer solrconfig.xml angegeben werden oder die Solr-Abfrage-URL überschreiben.
Kurz gesagt: Sie machen sich Gedanken darüber, welche Felder und Boosts Sie bei der Konfiguration verwenden möchten, und Ihre Benutzer geben Ihnen einfach Wörter vor, ohne sich allzu viele Gedanken über die Syntax zu machen.
Der Zauber von dismax liegt (meiner Meinung nach) in der Abfragestruktur, die es erzeugt. Im Wesentlichen handelt es sich um eine Matrixmultiplikation: eine einspaltige Matrix jedes „Chunks“ Ihrer Benutzereingabe, multipliziert mit einer einzeiligen Matrix der Felder von qf
, um eine große Matrix mit jeder Feld:Chunk-Permutation zu erzeugen. Die Matrix wird dann in eine BooleanQuery umgewandelt, die aus DisjunctionMaxQueries für jede Zeile in der Matrix besteht. Die DisjunctionMaxQuery wird verwendet, weil ihre Punktzahl durch die maximale Punktzahl ihrer Unterklauseln bestimmt wird – statt durch die Summe wie bei einer BooleanQuery – so dass kein einziges Wort aus der Benutzereingabe die endgültige Punktzahl dominiert. Am besten lässt sich dies anhand eines Beispiels erklären. Betrachten wir also die folgende Eingabe…
defType = dismax mm = 50% qf = features^2 name^3 q = +"apache solr" search server
Als erstes betrachten wir die „Markup“-Zeichen des Parsers, die in dieser q
Zeichenfolge erscheinen:
- Leerzeichen – Aufteilung des Eingabe-Strings in Abschnitte
- Anführungszeichen – macht einen einzelnen Satzteil zu einem Chunk
- + – macht einen Chunk obligatorisch
Wir haben also 3 „Brocken“ von Benutzereingaben:
- „apache solr“ (muss übereinstimmen)
- „Suche“ (sollte übereinstimmen)
- „Server“ (sollte übereinstimmen>
Wenn wir das mit unserer Liste qf
(features, name)
„multiplizieren“, erhalten wir eine Matrix wie diese…
merkmale: „apache solr“ | name: „apache solr“ | (muss übereinstimmen) |
features: „Suche“ | Name: „Suche“ | (sollte übereinstimmen) |
Eigenschaften: „Server“ | Name: „Server“ | (sollte übereinstimmen) |
Wenn wir dann den Parameter mm
zur Bestimmung der „Mindestanzahl von ‚ShouldMatch‘-Klauseln, die übereinstimmen müssen“ (50% von 2 == 1) berücksichtigen, erhalten wir die folgende Abfragestruktur (in Pseudocode)…
q = BooleanQuery( minNumberShouldMatch => 1, booleanClauses => ClauseList( MustMatch(DisjunctionMaxQuery( PhraseQuery("features","apache solr")^2, PhraseQuery("name","apache solr")^3) ), ShouldMatch(DisjunctionMaxQuery( TermQuery("features","search")^2, TermQuery("name","search")^3) ), ShouldMatch(DisjunctionMaxQuery( TermQuery("features","server")^2, TermQuery("name","server")^3)) ));
Mit mir so weit rechts?
Der Punkt, an dem die Leute oft stolpern, ist die Frage, wie sich die Solr-Konfiguration für die Analyse pro Feld (in schema.xml) auf all dies auswirkt. Unser obiges Beispiel war ziemlich einfach, aber lassen Sie uns einen Moment überlegen, was passieren könnte, wenn:
- Das Feld
name
verwendet den WordDelimiterFilter zur Abfragezeit,features
jedoch nicht. - Das Feld
features
ist so konfiguriert, dass „the“ ein Stoppwort ist,name
jedoch nicht.
Schauen wir uns nun an, was wir erhalten, wenn unsere Eingabeparameter strukturell ähnlich wie zuvor sind, aber gerade so unterschiedlich, dass WordDelimiterFilter und StopFilter ins Spiel kommen…
defType = dismax mm = 50% qf = features^2 name^3 q = +"apache solr" the search-server
Unsere resultierende Abfrage wird in etwa so aussehen…
q = BooleanQuery( minNumberShouldMatch => 1, booleanClauses => ClauseList( MustMatch(DisjunctionMaxQuery( PhraseQuery("features","apache solr")^2, PhraseQuery("name","apache solr")^3) ), ShouldMatch(DisjunctionMaxQuery( TermQuery("name","the")^3) ), ShouldMatch(DisjunctionMaxQuery( TermQuery("features","search-server")^2, PhraseQuery("name","search server")^3)) ));
Die Verwendung von WordDelimiterFilter hat die Dinge nicht sehr verändert: Features behandelt „search-server“ als einen einzelnen Term, während wir im Feld name
nach der Phrase „search server“ suchen – das sollte hoffentlich niemanden überraschen, wenn man die Verwendung von WordDelimiterFilter für das Feld name bedenkt (vermutlich wird er deshalb verwendet). Diese DisjunctionMaxQuery macht immer noch „Sinn“, aber andere Felder mit seltsamen Analysen, die weniger/mehr Token als ein „typisches“ Feld für denselben Thunk erzeugen, könnten Abfragen erzeugen, die nicht so leicht zu verstehen sind. Betrachten Sie insbesondere, was in unserem Beispiel mit dem Wort „the“ passiert ist: Da „the“ ein Stoppwort im Feld features
ist, wird für diese Feld/Chunk-Kombination kein Abfrageobjekt erzeugt. Für das Feld name
wird jedoch eine Abfrage erstellt, was bedeutet, dass die Gesamtzahl der „ShouldMatch“-Klauseln in unserer Abfrage auf oberster Ebene immer noch 2 beträgt, so dass unsere minNumberShouldMatch immer noch 1 ist (50% von 2 == 1).
Diese Art von Situation verwirrt viele Leute: Da „the“ ein Stoppwort in einem Feld ist, erwarten sie nicht, dass es in der endgültigen Abfrage eine Rolle spielt – aber solange mindestens ein qf
Feld ein Token dafür erzeugt (name
in unserem Beispiel), wird es in der endgültigen Abfrage enthalten sein und zur Anzahl der „ShouldMatch“-Klauseln beitragen.
Was ist also die Quintessenz aus all dem?
DisMax ist ein kompliziertes Gebilde. Wenn Sie es verwenden, müssen Sie alle Optionen sorgfältig abwägen und sich die Ausgabe von debugQuery=true
ansehen, während Sie mit verschiedenen Abfragezeichenfolgen und verschiedenen Analysekonfigurationen experimentieren, um wirklich sicher zu sein, dass Sie verstehen, wie die Abfragen Ihrer Benutzer geparst werden.