Erforschung der TrieRange-Funktionen von Lucene und Solr
Kürzlich haben Uwe Schindler und andere eine neue Funktion zu Lucene und Solr hinzugefügt, die die Arbeit mit numerischen Bereichen erheblich beschleunigt. Ich habe diese neue Funktion noch nicht ausprobiert, also dachte ich, ich gehe sie hier durch und erkunde ihre Möglichkeiten.
Da Lucene fast alles als Strings behandelt, war die Kodierung von Zahlen und Datumsangaben und ihre anschließende Verwendung in Bereichen schon immer mit etwas Mehraufwand verbunden, um eine gute Leistung zu erzielen. Früher musste man entweder weniger präzise oder langsamer laufende Abfragen verwenden, um mit Bereichen zu arbeiten, die viele unterschiedliche Werte enthielten. Dies ist darauf zurückzuführen, dass Lucene eine große Anzahl von Begriffen aufzählen muss.
Dank des neuen Pakets org.apache.lucene.search.trie (das sich derzeit im Bereich contrib/queries von Lucene befindet, aber möglicherweise in den Lucene-Kern verschoben wird, siehe [1] unten) und seiner Ergänzung zu Solr über einen FieldType können Lucene- und Solr-Benutzer nun die Vorteile einer viel schnelleren Bereichssuche nutzen.
Um es auszuprobieren, habe ich Solr wie folgt ausprobiert:
Zunächst habe ich die FieldType-Definitionen aus dem Solr-Beispielschema entnommen:
<fieldType name="tint" class="solr.TrieField" type="integer" omitNorms="true" positionIncrementGap="0" indexed="true" stored="false" />
<fieldType name="tint4" class="solr.TrieField" precisionStep="4" type="integer" omitNorms="true" positionIncrementGap="0" indexed="true" stored="false" />
<fieldType name="tfloat" class="solr.TrieField" type="float" omitNorms="true" positionIncrementGap="0" indexed="true" stored="false" />
<fieldType name="tlong" class="solr.TrieField" type="long" omitNorms="true" positionIncrementGap="0" indexed="true" stored="false" />
<fieldType name="tdouble" class="solr.TrieField" type="double" omitNorms="true" positionIncrementGap="0" indexed="true" stored="false" />
<fieldType name="tdouble4" class="solr.TrieField" type="double" precisionStep="4" omitNorms="true" positionIncrementGap="0" indexed="true" stored="false" />
Als nächstes habe ich ein paar Felder deklariert. In meinem Fall habe ich einfach eine Reihe von dynamischen Feldern verwendet, weil ich mir über die tatsächlichen Feldnamen keine Gedanken mache:
<field name="id" type="string" indexed="true" stored="true" />
<dynamicField name="*_i" type="integer" indexed="true" stored="true"/>
<dynamicField name="*_f" type="float" indexed="true" stored="true"/>
<dynamicField name="*_d" type="double" indexed="true" stored="true"/>
<dynamicField name="*_l" type="long" indexed="true" stored="true"/>
<dynamicField name="*_si" type="sint" indexed="true" stored="true"/>
<dynamicField name="*_s" type="string" indexed="true" stored="true"/>
<dynamicField name="*_sl" type="slong" indexed="true" stored="true"/>
<dynamicField name="*_t" type="text" indexed="true" stored="true"/>
<dynamicField name="*_b" type="boolean" indexed="true" stored="true"/>
<dynamicField name="*_sf" type="sfloat" indexed="true" stored="true"/>
<dynamicField name="*_sd" type="sdouble" indexed="true" stored="true"/>
<dynamicField name="*_dt" type="date" indexed="true" stored="true"/>
<dynamicField name="*_tri" type="tint" indexed="true" stored="true"/>
<dynamicField name="*_tri4" type="tint4" indexed="true" stored="true"/>
<dynamicField name="*_trf" type="tfloat" indexed="true" stored="true"/>
<dynamicField name="*_trl" type="tlong" indexed="true" stored="true"/>
<dynamicField name="*_trd" type="tdouble" indexed="true" stored="true"/>
<dynamicField name="*_trd4" type="tdouble4" indexed="true" stored="true"/>
Jetzt ist es an der Zeit, dass der Gummi auf die Straße trifft, wie man so schön sagt. Mit anderen Worten, wir müssen ein paar Dokumente einfügen und suchen. In meinem Fall werde ich drei Felder für jeden numerischen Wert erstellen, eines als „normales“ Primitiv, eines als „sortierbares“ Primitiv und eines als Trie*-Feld, nur um einige der Unterschiede zu verdeutlichen. Mein SolrInputDocument sieht wie folgt aus:
SolrInputDocument result = new SolrInputDocument(); result.addField(idField, id); addField(result, "integer_i", "integer_si", "integer_tri", random.nextInt(10000));//get only positive values addField(result, "float_f", "float_sf", "float_trf", random.nextFloat()*100); addField(result, "long_l", "long_sl", "long_trl", Math.abs(random.nextLong())); double dbl = random.nextDouble() * 100; addField(result, "double_d", "double_sd", "double_trd", dbl); result.addField("double_trd4", dbl);
(addField fügt den Wert nur dreimal zum Dokument hinzu, einmal für jeden Feldnamen. Ich hätte stattdessen copyField in der schema.xml verwenden können, aber ich wollte sie explizit während der Erstellung des Dokuments deklarieren, damit ich das Schema nicht später bearbeiten muss, wenn ich meine Meinung ändere). Ich habe dann einen Index mit 10K Dokumenten erstellt.
Lassen Sie uns mit einigen Grundlagen beginnen. Zunächst möchte ich alle Dokumente finden, die einen ganzzahligen Wert zwischen 0 und 5.000 haben.
Natürlich ist das der falsche Weg, dies zu tun:
In diesem Fall erhalten Sie die lexikografische Sortierung und nicht die numerische Sortierung, was zu Ergebnissen führt, wenn auch zu schlechten. Schlechter Grant.
Dann gehe ich zur sortierbaren Version über und verwende die gleiche Abfrage, ändere aber das Feld in integer_si. Dies führt nun zu korrekten Ergebnissen. Hier gibt es nicht viel zu sagen, außer dass es funktioniert.
Schließlich habe ich die Trie-Sache ausprobiert, indem ich die Abfrage so geändert habe, dass sie integer_tri verwendet. Die Ergebnisse sind die gleichen wie bei der sortierbaren Abfrage, aber in meinem völlig informellen Tests ist die Trie-Sache viel schneller (andere haben formellere Tests durchgeführt, so dass ich mit meinen Ergebnissen zufrieden bin). Gute Nachrichten!
Als Nächstes habe ich es mit viel mehr Dokumenten (1M) versucht und ziemlich genau die gleichen Geschwindigkeitssteigerungen festgestellt. In diesem Fall habe ich sogar versucht, einen viel größeren Bereich (50K) zu erhalten, der zwar langsamer ist als der kleinere Bereich, aber immer noch schneller als der sortierbare Ansatz.
Natürlich kratzt das nur an der Oberfläche. Das Wichtigste ist jedoch, dass die neuen Trie-Funktionen in L/S vielversprechend sind, wenn es darum geht, bereichsbasierte numerische Abfragen zu beschleunigen und die Grenze zwischen Suchmaschinen und Datenbanken weiter zu verwischen (ich würde behaupten, dass die Suche dadurch noch attraktiver wird, aber…). Im Wesentlichen formalisiert es das, was viele Leute in der Praxis über die Jahre mit verschiedenen Feldwerten gemacht haben. Siehe [2] für weitere Details.
Halten Sie Ausschau nach dem Trie Range Material (möglicherweise umbenannt), das offiziell in Lucene 2.9 und Solr 1.4 veröffentlicht wird.
Referenzen: