Beispiel für End-to-End-Nutzdaten in Solr
Hinweis: Dieses Beispiel und die unten beschriebene Lösung sind in den meisten Fällen nicht mehr notwendig. Wenn Sie Solr 6.6…
Hinweis: Dieses Beispiel und die unten beschriebene Lösung sind in den meisten Fällen nicht mehr notwendig. Wenn Sie Solr 6.6 oder höher verwenden, wurde die Unterstützung für Payloads zu Solr hinzugefügt. Lesen Sie dazu den Abschnitt „Payload Query Parsers“ im Solr-Referenzhandbuch. Die Technik in diesem Beitrag kann immer noch zur Spezialisierung der Payload-Nutzung über die native Solr-Unterstützung hinaus verwendet werden.
Payloads sind prima, aber wo ist ein vollständiges Beispiel für Solr?
Immer wenn ich über Payloads in Solr spreche, war ich etwas frustriert, weil ich kein Beispiel finden konnte, das mir alle Teile an einem einzigen Ort zur Verfügung stellte. Also habe ich mich entschlossen, eines für Solr 4.0+ zu erstellen (eigentlich 4.8.1 zum Zeitpunkt der Erstellung dieses Artikels, aber das sollte für alle 4x-Codezeilen gelten). Es gibt viele hilfreiche Fragmente. Unser eigener Grant Ingersoll zeigte 2009 (https://lucidworks.com/2009/08/05/getting-started-with-payloads/), wie man Payloads auf Lucene-Ebene verwendet.
Seitdem wurden Nutzdaten zu Solr hinzugefügt…irgendwie. Es gibt die DelimitedPayloadTokenFilterFactory, die Sie verwenden können, wenn Sie eine Analysekette in schema.xml erstellen, die begrenzte Payload-Token akzeptiert und die Payload zusammen mit Term speichert. Dieser fieldType und dieses Feld sind sogar in der Standardverteilung enthalten.
Die Frage ist jedoch, wie Sie Payloads bei Abfragen in Solr verwenden? Dieser Beitrag enthält ein durchgängiges Beispiel.
Zunächst ein kurzer Rückblick
Nutzdaten sind eine Möglichkeit, einen numerischen Wert mit einem Begriff zu verknüpfen. Wenn also ein Begriff in der Abfrage mit einem Begriff im Dokument übereinstimmt, steht Ihnen auch der numerische Wert zur Verfügung, den Sie für die Auswertung verwenden können. Es gibt eine Vielzahl von Verwendungsmöglichkeiten für Payloads, hier sind ein paar davon:
- Teile der Sprache. Nehmen wir an, Sie möchten Substantive stärker gewichten als Adjektive. Lassen wir das Problem der Erkennung von Substantiven und Adjektiven einmal beiseite… nehmen wir an, Sie können das. Sie können eine Gewichtung zuordnen, die Sie dann in die Bewertung einbeziehen können, indem Sie Substantive, die mit Begriffen aus der Suche übereinstimmen, stärker gewichten.
- Heuristisch entdeckte Korrelationen, auch bekannt als „geheime Soße“. Sie haben das Nutzungsverhalten analysiert und festgestellt, dass, wenn der erste Suchbegriff das Wort „Angeln“ enthält, die Wahrscheinlichkeit hoch ist, dass der Nutzer eher einen Köder als einen Echolot kauft. Wann immer Sie das Wort „Angeln“ in der Beschreibung eines Produkts finden, das Sie als „Köder“ kategorisiert haben, fügen Sie diesem Begriff ein gewisses Gewicht zu.
- Wann immer Sie ein Verhalten mit bestimmten Begriffen in Verbindung bringen können, können Sie diese Begriffe bei der Berechnung des Scores von Solr-Dokumenten stärker gewichten. Diese Art der Verarbeitung kann sehr rechenintensiv sein und ist daher zur Suchzeit nicht sehr performant. Wenn Sie diese Verarbeitung durch das Hinzufügen von Nutzdaten in die Indexierungszeit verlagern können, können Sie die Ergebnisse der Berechnung dieser Korrelationen verwenden und trotzdem eine performante Suche durchführen.
Gliederung der Schritte
Vergessen Sie nicht, dass ich nicht darauf eingehe , wie Sie die Zusammenhänge herstellen.
- Fügen Sie die Nutzlast dem Begriff im Dokument hinzu.
- Ändern Sie Ihre schema.xml-Datei, damit Sie diese Nutzdaten verwenden können.
- Ändern Sie Ihre solrconfig.xml, um den neuen Abfrageparser, den Sie schreiben werden, zu erkennen.
- Schreiben Sie eine neue Ähnlichkeitsklasse für das Feld payloaded.
- Verwenden Sie den neuen Abfrageparser in Abfragen.
Keiner dieser Schritte ist sonderlich schwierig, aber es kann mühsam sein, alle Teile ohne Anleitung zu verbinden. Hier ist das Kochbuch.
Fügen Sie den geladenen Begriff zum Dokument hinzu.
Dies ist eigentlich der einfachste Teil, verwenden Sie einfach ein Pipe-Trennzeichen. Ihr Begriff sieht dann aus wie „fishing|5.0“.
Ändern Sie Ihre schema.xml-Datei.
Ihre Datei schema.xml wird zwei Änderungen aufweisen. Das Standardschema enthält einen Feldtyp „payload“, den wir ändern werden. Das Standardschema enthält auch ein Feld („payloads“), das diesen Feldtyp bereits verwendet. Es sieht wie folgt aus:
<field name="payloads" type="payloads" indexed="true" stored="true"/>
Für die Auswertung der Daten funktioniert dies einwandfrei, aber wir müssen eine Änderung vornehmen, um dies für die Auswertung zu verwenden: Fügen Sie eine neue benutzerdefinierte Ähnlichkeit zum fieldType hinzu. Ihr <fieldType> wird wie folgt aussehen:
<fieldtype name="payloads" stored="false" indexed="true" class="solr.TextField" > <analyzer> <tokenizer class="solr.WhitespaceTokenizerFactory"/> <filter class="solr.DelimitedPayloadTokenFilterFactory" encoder="float"/> </analyzer> <similarity class="payloadexample.PayloadSimilarityFactory" /> </fieldtype>
Die Ähnlichkeitsklasse ist ein benutzerdefinierter Code, den Sie später sehen werden. Ihre Aufgabe ist es, die Nutzdaten zu nehmen und sie zu verwenden, um die Bewertung des Dokuments zu beeinflussen. Eine Funktion von Solr 4.x ist, dass Sie benutzerdefinierte Ähnlichkeiten für einzelne Felder definieren können, was wir hier getan haben.
Es gibt noch eine weitere Änderung, die Sie an dem Schema vornehmen müssen. Ganz unten finden Sie einen Kommentar über benutzerdefinierte Ähnlichkeiten. Fügen Sie diese Zeile hinzu:
<similarity class="solr.SchemaSimilarityFactory"/>
Dadurch wird Ihre Ähnlichkeit in der <fieldType> gefunden.
Ändern Sie Ihre Datei solrconfig.xml.
So weit, so gut. Aber wie verwenden Sie das eigentlich? Es stellt sich heraus, dass, wenn Sie keinen eigenen Parser erstellen, die Standardauswertung der Nutzlast einfach 1.0f zurückgibt. Sie müssen also einen Parser erstellen, der den Wert tatsächlich verwendet. Sie müssen zwei Änderungen vornehmen: Definieren Sie den lib-Pfad für Ihr jar und definieren Sie einen neuen Abfrageparser. Dies sieht folgendermaßen aus:
<lib dir="path_to_jar_file_containing_custom_code" regex=".*.jar"> and then: <queryParser name="myqp" class="payloadexample.PayloadQParserPlugin" />
Schreiben Sie eine neue Ähnlichkeitsklasse und einen Abfrageparser für das Feld payloaded.
OK, hier ist der Code. Dies ist der längste Teil des Beitrags, also haben Sie bitte etwas Geduld mit mir. Oder überspringen Sie das Ende und fügen Sie den Code später ein. Es gibt zwei Dateien, die ich in meinem Beispielcode in das gleiche jar packe. Zunächst das PayloadQParserPlugin (siehe die Änderungen in solrconfig.xml).
package payloadexample; import org.apache.lucene.index.Term; import org.apache.lucene.search.Query; import org.apache.lucene.search.payloads.AveragePayloadFunction; import org.apache.lucene.search.payloads.PayloadTermQuery; import org.apache.solr.common.params.CommonParams; import org.apache.solr.common.params.SolrParams; import org.apache.solr.common.util.NamedList; import org.apache.solr.parser.QueryParser; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.schema.SchemaField; import org.apache.solr.search.QParser; import org.apache.solr.search.QParserPlugin; import org.apache.solr.search.QueryParsing; import org.apache.solr.search.SyntaxError; // Just the factory class that doesn't do very much in this // case but is necessary for registration in solrconfig.xml. public class PayloadQParserPlugin extends QParserPlugin { @Override public void init(NamedList args) { // Might want to do something here if you want to preserve information for subsequent calls! } @Override public QParser createParser(String qstr, SolrParams localParams, SolrParams params, SolrQueryRequest req) { return new PayloadQParser(qstr, localParams, params, req); } } // The actual parser. Note that it relies heavily on the superclass class PayloadQParser extends QParser { PayloadQueryParser pqParser; public PayloadQParser(String qstr, SolrParams localParams, SolrParams params, SolrQueryRequest req) { super(qstr, localParams, params, req); } // This is kind of tricky. The deal here is that you do NOT // want to get into all the process of parsing parentheses, // operators like AND/OR/NOT/+/- etc, it's difficult. So we'll // let the default parsing do all this for us. // Eventually the complex logic will resolve to asking for // fielded query, which we define in the PayloadQueryParser // below. @Override public Query parse() throws SyntaxError { String qstr = getString(); if (qstr == null || qstr.length() == 0) return null; String defaultField = getParam(CommonParams.DF); if (defaultField == null) { defaultField = getReq().getSchema().getDefaultSearchFieldName(); } pqParser = new PayloadQueryParser(this, defaultField); pqParser.setDefaultOperator (QueryParsing.getQueryParserDefaultOperator(getReq().getSchema(), getParam(QueryParsing.OP))); return pqParser.parse(qstr); } @Override public String[] getDefaultHighlightFields() { return pqParser == null ? new String[]{} : new String[] {pqParser.getDefaultField()}; } } // Here's the tricky bit. You let the methods defined in the // superclass do the heavy lifting, parsing all the // parentheses/AND/OR/NOT/+/- whatever. Then, eventually, when // all that's resolved down to a field and a term, and // BOOM, you're here at the simple "getFieldQuery" call. // NOTE: this is not suitable for phrase queries, the limitation // here is that we're only evaluating payloads for // queries that can resolve to combinations of single word // fielded queries. class PayloadQueryParser extends QueryParser { PayloadQueryParser(QParser parser, String defaultField) { super(parser.getReq().getCore().getSolrConfig().luceneMatchVersion, defaultField, parser); } @Override protected Query getFieldQuery(String field, String queryText, boolean quoted) throws SyntaxError { SchemaField sf = this.schema.getFieldOrNull(field); // Note that this will work for any field defined with the // <fieldType> of "payloads", not just the field "payloads". // One could easily parameterize this in the config files to // avoid hard-coding the values. if (sf != null && sf.getType().getTypeName().equalsIgnoreCase("payloads")) { return new PayloadTermQuery(new Term(field, queryText), new AveragePayloadFunction(), true); } return super.getFieldQuery(field, queryText, quoted); } }
Was hat es mit der AveragePayloadFunction() auf sich? Nun, stellen Sie sich vor, dass Sie mehrere Begriffe im selben Dokument haben, die jeweils unterschiedliche Nutzwerte haben. Diese Funktion wird „das Richtige tun“, wenn der Durchschnitt dieser Werte „das Richtige“ ist. Es gibt einige vordefinierte Nutzdatenfunktionen (die alle von PayloadFunction abgeleitet sind), die in anderen Fällen „das Richtige“ mit den Nutzdaten tun, z.B. min, max, die Sie ebenfalls verwenden können. Oder Sie können Ihre eigenen Funktionen schreiben, wenn Sie andere Anforderungen haben.
Jetzt die PayloadSimilarityFactory (siehe die Änderungen in schema.xml)
package payloadexample; import org.apache.lucene.analysis.payloads.PayloadHelper; import org.apache.lucene.search.similarities.DefaultSimilarity; import org.apache.lucene.search.similarities.Similarity; import org.apache.lucene.util.BytesRef; import org.apache.solr.common.params.SolrParams; import org.apache.solr.schema.SimilarityFactory; public class PayloadSimilarityFactory extends SimilarityFactory { @Override public void init(SolrParams params) { super.init(params); } @Override public Similarity getSimilarity() { return new PayloadSimilarity(); } } class PayloadSimilarity extends DefaultSimilarity { //Here's where we actually decode the payload and return it. @Override public float scorePayload(int doc, int start, int end, BytesRef payload) { if (payload == null) return 1.0F; return PayloadHelper.decodeFloat(payload.bytes, payload.offset); } }
Denken Sie daran: Computer sind dumm. Irgendwo müssen Sie dem Computer sagen, dass er sich die Nutzlast aus dem Begriff holen und sie verwenden soll. Das ist alles, was Sie hier tun. Die Standardeinstellung scorePayload gibt einfach 1.0 zurück. Wenn Sie diesen Schritt nicht tun, werden Sie sich wundern, warum Ihre Payloads überhaupt keine Wirkung haben.
Verwenden Sie den neuen Abfrageparser in Abfragen.
Zunächst ein kurzer Rückblick auf das, was wir bisher getan haben:
- Nutzdaten wurden der Eingabedatei mit dem Trennzeichen Pipe (|) hinzugefügt.
- Ändern Sie die Konfigurationsdateien, um die Nutzdaten in den Index aufzunehmen.
- Benutzerdefinierter Code für eine neue Ähnlichkeitsklasse und einen Abfrageparser hinzugefügt, um etwas mit der Nutzlast zu tun.
Genau wie beim Rest von Solr ist die tatsächliche Nutzung dieser Informationen eine Frage des Abfrageparsers, der sie tatsächlich abruft. Der Payload Query Parser ist genau wie jeder andere Query Parser, ob Edismax, Standard, Term, Phrase, Raw, Nested oder was auch immer (siehe: die CWiki-Dokumente). Er muss trotzdem aufgerufen werden.
Das ist eigentlich ganz einfach. Das Beispiel hier verwendet defType, aber Sie könnten genauso gut einen defType in einem Request Handler angeben, der diesen Query Parser in solrconfig.xml verwendet, ihn in verschachtelten Abfragen verwenden usw. Es handelt sich um einen Abfrageparser, den Sie wie jeden der im obigen Link erwähnten Parser aufrufen können.
http://localhost:8983/solr/collection1/query?defType=myqp&q=payloads:(electronics memory)
Beachten Sie, dass dieser nicht automatisch aufgerufen wird, wenn Sie einen anderen Abfrageparser verwenden. Wenn Sie zum Beispiel defType=edismax verwenden, wird dieser Abfrageparser nicht aufgerufen. Für diese Qualität müssen Sie sich die zusätzliche Arbeit machen, einen fieldType zu definieren.
Fazit
Ich hoffe, dass dieses vollständige Beispiel es für andere einfacher macht, alle Teile miteinander zu verbinden und Nutzdaten von Solr zu verwenden, ohne zu viel herumwühlen zu müssen. Dieser spezielle Code hat jedoch einige Einschränkungen:
- Es kann Phrasen nicht gut verarbeiten. Es erstellt eine „TermQuery“ der gesamten Phrase, was nicht das ist, was Sie wollen.
- Es erfordert einige Investitionen in den Code. Es wäre schöner, wenn Solr eine native Unterstützung hätte.
- Sie gilt nicht für alle verschiedenen Query-Parser, sondern nur für den neu definierten qparser.
- Ich habe mich mit Chris Hostetter unterhalten (er ist mein Ansprechpartner für alles, was mit Abfrageparsern zu tun hat) und dieser Ansatz hat diesen Vorzug, Zitat:
..you can use that qparser with any field type -- a custom one that adds payloads, PreAnalyzedField, TextField using DelimitedPayloadTokenFilterFactory, whatever...
- Der andere Ansatz wäre, einen benutzerdefinierten FieldType zu erstellen. Der Vorteil (wieder ein Zitat) ist
If you go the custom FieldType approach, then you automatically get payload based queries from most of the existing query parsers -- but you have to decide (and as a result: constrain) when/how/why payloads are added to your terms in the FieldType logic
- Ich habe mich mit Chris Hostetter unterhalten (er ist mein Ansprechpartner für alles, was mit Abfrageparsern zu tun hat) und dieser Ansatz hat diesen Vorzug, Zitat:
Jedes hat also seine Berechtigung, es geht nur darum, sie irgendwann in Solr zu implementieren. Siiighhh. Zusammen mit den anderen 50 Dingen, die ich gerne tun würde.
Erick Erickson, 13-Juni-2014