Edismax, Solr4 und PHP

Wir nutzen Solr als eine überlegene Möglichkeit, große Mengen von Daten nach Begriffen zu durchsuchen. Die Suche mit direkten Datenbankanfragen ist zu langsam, und das Aufbereiten der Daten nach Relevanz der Ergebnisse ist kompliziert. Solr verwaltet eine Indizierung von Suchwörtern und anderen Kriterien und kann Relevanzkriterien wie zB Häufigkeit oder relative Häufigkeit mit verwalten.

Mit Solr 4 kommen noch so schöne Eigenschaften hinzu wie Suche nach geographischen Daten und Ranking.

Ich will vorallem die Nutzung von Edismax in PHP erklären.

SolrQuery

Um Solr im PHP nutzen zu können, verwenden wir die mitgelieferte Solr-Extension von PHP. Eine Übersicht über die Erweiterung steht hier: <a

href=“http://www.php.net/manual/de/intro.solr.php“>link.  Um eine Abfrage an Solr zu starten, muß man eine Verbindung zum Solr schaffen, und ein Query Objekt erzeugen. Das Query Objekt wird im weiteren unsere Basis sein für die verwendeten Parameter.

Boilerplate einer Solr-Abfrage, Teil 1

 

Eine simple Abfrage

Um die Anfrage durchzuführen wird dem Query-Objekt mit dem Minimum an notwendigen Daten zugewiesen. Diese Code-Zeilen werden sich im weiteren nicht ändern, ich werde zeigen, daß alles weitere mit Filtern und anderen Parametern geregelt werden kann.

Boilerplate einer Solr-Abfrage, Teil 2

 

Query, Filterqueries, Parameter und Rankingfunktionen

Query

Es kann immer nur eine Query-Parameter pro Abfrage gestellt werden. Für die Query gibt es eine Basis-Syntax wie zB Wildcards. Es gibt logische Verknüpfungen und Suche in bestimmten Feldern, aber auch komplexe Funktionen. Letztendlich wird dies alles verwenden zum Bestimmen eines Rankings. Bis auf Wildcards und logische Verknüpfungen einzelner Suchbegriffe kann man alles weitere in anderen Parametern unterbringen – und das sollte man auch tun um die Query möglichst simpel zu halten.

Eine Query beeinflusst das Ranking

Filterquery

Filterqueries können beliebig viele gestellt werden. Filterqueries beeinflussen nicht das Ranking und werden im Solr gecached, sie beschleunigen daher die Abfragen erheblich – was jedoch auch bedeutet, daß man sich Gedanken machen sollte welche Queries man cached um den Cache nicht unnötig zu „verschmutzen“. Filterqueries werden meist genutzt um Ausschlußkriterien mitzugeben, die nicht in das Ranking mit einfliessen.

Eine Filterquery beeinflusst das Ranking nicht

Parameter

Einige Parameter können das Ranking generell beeinflussen. Die meisten Rankings nach Wörtern laufen nach einem bestimmten Schema. Man kann für dieses Schema bestimmte Parameter beeinflussen wie zB den Boost bestimmter Felder.

Ein Parameter kann das Ranking beeinflussen

Ranking funktion

Neben dem Ranking über die Query gibt es eine Reihe von weiteren Möglichkeiten, das Ranking zu beeinflussen.

Alle diese Möglichkeiten werden in einer Rankingfunktion zusammengefasst. Es gibt immer nur eine Rankingfunktion pro Query. Die Rankingfunktion wird mit dem Rankingergebniss multipliziert.

Um Verwirrung zu vermeiden, die Rankingfunktion wird oft als Synonym verwendet für Boostfunktion, und es gibt mehrere simple oder solrspezifische Funktionen (wie zB Addition oder Dokumentlänge), die Teil der Rankingfunktion sein können.

Edismax einschalten und die Basics festlegen.

Zuerst muß Solr mitgeteilt werden, was für einen Parser man nutzen will.

Im Standard ist das Lucene, wir wollen aber Edismax:

$query->setParam('defType', 'edismax');

Als nächstes teilen wir Solr mit, was die Suchfelder sind – d.h. in welchen Feldern nach den Worten in der Query gesucht werden
soll. Für das kann man auch einen Default im Solr-Schema festlegen, aber
es über das Program zu konfigurieren ermöglicht uns verschiedene Abfragen
zu unterschiedlichen Zwecken zu stellen.

Der Parameter dafür heißt uf für Userfield

$query->setParam('uf', "title subTitle regionText keywords description")

Jetzt können wir (müssen aber nicht) die Rückgabefelder

festlegen. Die Rückgabefelder können interessant sein, wenn wir auf Interna der Solr-Suche zurückgreifen wollen, wie zB bei der Geosuche auf die ermittelte Distanz. Der Parameter dafür heißt fl für Fieldlist

$query->setParam('fl',"id title subTitle regionList zipList dateCreated);

Geodaten als Filter

Einer der nützlichen Erweiterungen ist das Suchen nach mehreren Geodaten.
Jedoch funktionierte in unserer ersten Version von Solr noch nicht alles so wie es sollte. Wir haben uns deswegen auf die Sachen beschränkt, die
schon klappen.

Geodaten sind insofern eine lehrreiche Sache, weil damit der Unterschied zwischen Filter und Boost deutlich wird:

  • angenommen wir wollen nur Suchergebnisse, bei denen der Ort in einem
    bestimmten Bereich liegt, zB 50km, dann nehmen wir dafür einen Filter
  • wollen wir jedoch innerhalb dieses Bereiches die Treffer höher
    bewerten, die näher am Ziel liegen, dann nehmen wir dafür eine Boost-Funktion

Die Filterfunktion in ihrer einfachsten Version ist

$query->setParam('fq', "{!bbox pt=53,11 sfield=lonLat d=30

 

cache=false}");

im einzelnen bedeutet das, daß wir

  • um die Koordinate 53,11
  • in einem Abstand von 30 Kilometern suchen wollen
  • die Geodaten dafür befinden sich im Feld lonLat
  • das Ergebnis dieser Abfrage soll nicht für andere Abfragen gecached
    werden

Die Koordinaten des Datensatzes können auch „MultiValue“ sein, dann wird die Koordinaten des Datensatzes genommen, die dem Punkt am nächsten liegt. Hier gab es bei unserem Solr noch Probleme, die ‚geodist‘-Funtion kam nicht mit Multivalue zurecht, die Funktion ‚bbox‘ hatte damit keine Probleme.

Distanz in das Abfrage-Ergebnis

Manchmal wollen wir die von Solr ermittelte Distanz wissen und mit dem Suchergebnis erhalten. Dazu müssen wir Solr mitteilen, daß wir eine generelle Distanz ermitteln wollen. Das ist insofern wichtig, weil die Parameter innerhalb einer

Funktion nicht für Angaben ausserhalb der Funktion zur Verfügung stehen.

Das geht dann folgendermaßen:

$query->setParam('sfield', "lonLat");
$query->setParam('pt', "53,11");
$query->setParam('fl', "* _dist_:geodist() ");
$query->setParam('fq', "{!bbox d=30 cache=false}");

Die wichtigen Parameter sind aus der Filterquery ausgelagert in eigene Parameter. Sie müssen dann auch in der Filterquery nicht mehr erscheinen.

In der Fieldlist steht dann eine Funktion „geodist“, die ihren Wert im Feld dist speichert.

Wichtig ist bei dieser Verwendung, daß man bei der Fieldlist ein * verwendet, was als Wildcard für alle weiteren Felder steht, ohne dieses Zeichen kriegt man nur die Distanz zurück. Man kann diese Felder natürlich auch einzelnen aufzählen, wenn einen zB nur einige Felder und nicht alle interessieren.

Allgemeine Hinweise zur Boostfunktion

Es gibt immer nur eine Boostfunktion, die mit Werten aus einer eigenen Berechnung gefüllt werden kann.

Solange man dafür nur einen Parameter verwendet, zählt allein das Verhältnis unterschiedlicher Werte dieses einen Parameters.

zB wenn bei Geodaten ein Treffer in einem Umkreis von 1km fünfmal höher bewertet werden soll als ein Treffer mit 30km Distanz, dann ist es egal, ob der Boost auf 1km der Wert 10 oder 100 ist, wenn der Wert auf 30km entsprechend 2 oder 20 ist.

Schwierig wird es erst, wenn man zwei unterschiedliche Werte verwendet, wie zB Distanz und Datum eines Eintrages. Und die Summe der beiden Werte als Boost verwendet.

Geodaten ins Ranking

Das Rankingergebnis der Suche kann mit der Boostfunktion multipliziert werden. Die Boostfunktion kann massiv das Ergebnis verändern, deswegen sollte man sich schon einige Gedanken machen, wie man diese Funktion berechnet.

Die Boostfunktion kann zB so aussehen:

$query->setParam('boost', "recip(geodist(),60,2000,250)");

In diesem Fall wird ein exakter Treffer etwa 5 mal höher bewertet als ein Treffer, der 15km entfernt ist, und 8 mal höher als ein Treffer der 30km entfern ist.

Recip

Die Recipe-Funktion soll ist eine Möglichkeit bieten, eine Rankingfunktion für steigende Werte in den Datensätzen zu erstellen, und zwar negativ korreliert, also höhere Werte sind schlechter. Das wäre zB der Fall, bei Datumsangaben (ältere Einträge sind schlechter) oder halt bei Distanzen (größere Abweichungen vom Ziel sind schlechter).

Die Recipe-Funktion lautet:

recip(x,a,b,c) = b / (a*x + c)

Man kann die Formel vereinfachen auf

recip(x,1,b,c) = b / (x + c)

ohne das man die ‚Möglichkeiten‘ der Bewertung einschränkt.

Setzt man noch vorraus, daß für x=0 der Wert = 1 sein soll, dann reduziert

sich die Formel auf:

recip(x,1,b,b) = b / (x + b)Die letzte Forderung macht

insofern Sinn, da man bei der Kombination von mehreren Boost-Faktoren (zB

Zeit und Distanz) eine Normierung haben will.

Daraus ergibt sich für b folgende Gleichung:

b = x * y / ( 1 - y)

wobei y der Faktor ist, den recip an der Stelle x haben soll.

Soll zB ein Eintrag in 30 km Distanz vom Zielort nur noch 80% des

ursprünglichen Wertes haben, dann ergibt sich für

b = 30 * 0.8 / (1-0.8) = 120

und die entsprechende recip Funktion lautet dann

recip(x,1,120,120).

Will man in eine kombinierte Funktion die Wertigkeit der einzelnen Boost-funktionen varieren, dann multipliziert man den zweiten Parameter mit dem Faktor, mit dem diese Funktion mehr Gewicht kriegen soll.

Recip-Werte für Datumsangaben

Bei Datumsangaben kann man die MS-Funktion verwenden, um die Differenz zwischen der aktuellen Zeit und dem Datumseintrag zu ermitteln. Dieser ist dann in Millisekunden.

recip(ms(NOW, dateCreated),1,b,b)

Will man nun für y (siehe Formel oben) einen Eintrag in Tagen haben, dann muß man die Anzahl der Tage multiplizieren mit 86400000 (= 1000 * 60 * 60 *24).

Daraus ergibt sich zB für den Parameter einer Anzeige, die nach 30 Tagen nur noch 20% des ursprüglichen Wertes haben soll:

recip(ms(NOW, dateCreated),1,648000000,648000000)

Boosting von Feldern

Felder können mit

$query->setParam('qf', "title^20 subtitle"); unterschiedlich

bewertet werden. So hat ein Eintrag mit dem obigen Parameter ein Treffer

im Feld „titel“ den 20fachen Wert eines Treffers im Feld „subtitle“.

Beim Ranking wird das Maximum der unterschiedlichen Felder genommen. Es würde in dem Fall meist der Wert des Feldes „Titel“ genommen werden.

Suche nach mehreren Suchworten in unterschiedlichen Feldern

Die Aussage, daß das Feld mit der höchstens Relevanz wird bewertet ist eine Simplifizierung des wirklichen Rankings.

Wenn man mehrere Suchbegriffe hat, dann werden verschiedene Kombinationen durchgetestet und davon das Maximum genommen. Angenommen wir haben folgenden Eintrag

title: "Java"

 

subtitle: "Python und Java"

und suchen nach „Python Java“.

Dann testet Solr sowohl die Kombination:

title=Java, subtitle=Python

als auch die Kombination

 

subtitle=Python Java

 

 

und

title=Java

durch.

Bei der ersten Kombination hat jedes Feld einen eigenen Rankingfaktor, dieser wird miteinander multipliziert.

Bei der zweiten und dritten Kombination hat nur ein Feld einen Rankingfaktor.

Solr berücksichtigt jetzt auch noch, wieviele Suchbegriffe insgesamt in der Kombination gefunden wurden, und bildet von allen drei Kombinationen das Maximum.

Das ganze ist zugegebenermaßen etwas komplex, und selbst wenn man die Rechnungen durchgeht oder es an Beispielen testet, können einem die Ergebnisse irritieren.

Warum kommt mein Eintrag nicht ?

Eine Möglichkeit zu testen, wie ein bestimmter Eintrag rankt ist:

Dann wird dieser Eintrag zusammen mit den anderen Queries ausgegeben, mit der entsprechenden Debug-Info kann man sich nun ansehen, wie das Ranking ermittelt wurde.

$query->setParam('explainOther', "id:245");

Relevanz von mehreren Feldern, und die Verknüpfen mit „tie“

Angenommen wir haben folgenden Eintrag:

title: "Python und Java"

 

subtitle: "Java"

und einen Eintrag

title: "Python und Java"

 

subtitle: "garnichts"

und suchen nach „Python Java“. In diesem Fall wollen wir das erste Ergebnis bevorzugen. Solr nutzt in diesem Fall nur das ‚title‘-Feld und blendet alle unterlegenen Kombinationen aus.

Wollen wir aber die unterlegenen Kombinationen mit berücksichtigen, dann nutzen wir den Parameter ‚tie‘. Tie bindet dieunterlegenen Kombinationen mit ein mit dem Faktor ‚tie‘. Tie muß also einen Wert haben zwischen 0 und 1.

Tags: