. .
MAI 2010

Solr ‘was finden? Geosuche mit Solr.

Eine Umkreissuche oder gar Distanzberechnungen zwischen Standorten sind aufwändig, kompliziert und lassen einen über Gebühr altern.

Jedenfalls dann, wenn man es selbst entwickelt. Wesentlich einfacher geht es auf Basis von Lucene. Oder, wenn man nur einen Nachmittag Zeit hat, mittels Solr.

Solr ist eine fertig konfigurierte Suchmaschine mit einem Lucene-Kern. Ein paar Anpassungen am Lucene-Schema und es kann losgehen.

Schritt 1: Solr für Spatial Search konfigurieren

Man installiert das Spatial Solr Plugin und konfiguriert es in der solrconfig.xml:

<queryParser name="spatial" class="nl.jteam.search.solrext.spatial.SpatialTierQueryParserPlugin" basedOn="dismax">
    <str name="corePoolSize">1</str>
    <str name="maxPoolSize">2</str>
    <str name="keepAlive">60</str>
    <str name="latField">lat</str>
    <str name="lngField">lng</str>
    <str name="tierPrefix">_tier_</str>
</queryParser>
 
<updateRequestProcessorChain>
    <processor class="nl.jteam.search.solrext.spatial.SpatialTierUpdateProcessorFactory">
        <str name="latField">lat</str>
        <str name="lngField">lng</str>
        <int name="startTier">9</int>
        <int name="endTier">17</int>
    </processor>
    <processor class="solr.LogUpdateProcessorFactory"/>
    <processor class="solr.RunUpdateProcessorFactory"/>
</updateRequestProcessorChain>

Schritt 2: Geodaten einspielen

Zufälligerweise haben wir keine Millionen Datensätze mit Geoinformationen herumliegen. Doch wozu sonst habe ich in den letzten Jahren die Leute von OpenStreetMap belächelt? Man schnappe sich eine OSM-Datei. Als kleinen Einstieg nehmen wir Nordrhein-Westfalen.

Diese Datei kann man nun von Hand parsen und als XML dem Server zu posten. Oder man nimmt mit dem DataImportHandler eine Abkürzung. Man definiert eine File-Datasource (oder alternativ eine Jdbc- oder Http-Datasource), verweist auf die OSM-Datei und legt ein Mapping zwischen OSM-Einträgen und Solr-Schema fest:

<dataconfig>
        <datasource type="FileDataSource" encoding="UTF-8" />
        <document>
        <entity name="page"
                processor="XPathEntityProcessor"
                stream="true"
                forEach="/osm/node"
                url="/Users/gru/Desktop/apache-solr-1.4.0/example/webapps/ROOT/nordrhein-westfalen.osm"
 
                >
            <field column="id"       xpath="/osm/node/@id"   commonField="true" />
            <field column="lat"  xpath="/osm/node/@lat"    commonField="true" />
            <field column="lng"      xpath="/osm/node/@lon" commonField="true" />
        <field column="author"      xpath="/osm/node/@user" commonField="true" />
            <field column="description"      xpath="/osm/node/tag/@v" commonField="true" />
       </entity>
        </document>
</dataconfig>

Nun wird Solr diese Datasource in der solrconfig.xml bekannt gemacht:

<requesthandler name="/dataimport" class="org.apache.solr.handler.dataimport.DataImportHandler">
    <lst name="defaults">
        <str name="config">data-config.xml</str>
    </lst>
</requesthandler>

Schritt 3: Solr starten

java -jar start.jar

Schritt 4: Import starten

http://localhost:8983/solr/dataimport?command=full-import

Je nach Ausgangsdatei vergeht die eine oder andere Minute. Am Ende jedoch sollte auf der Statistik-Seite (http://localhost:8983/solr/admin/stats.jsp) ungefähr folgender Wert erscheinen:

numDocs : 7199318

Demnach befinden sich nun im Lucene-Index über 7 Millionen geocodierte Einträge. Not bad.

Schritt 5: Die Suche

Mal sehen, ob wir etwas finden:

http://localhost:8983/solr/select/?q={!spatial%20lat=51.9%20long=8.9%20radius=10%20unit=km%20calc=arc%20threadCount=2}author:h*

Wir beauftragen hiermit Solr nach allen Einträgen eines Autors, dessen Name mit “h” beginnt, zu suchen, die sich auf einen Ort in einem Umkreis von 10 Kilometern um eine bestimmte Geokoordinate beziehen.

Schritt 6: Lasttest

Eine einzelne Abfrage ist natürlich keine Herausforderung für Solr. Also quälen wir das MacBook mit einem Lasttest über JMeter.

Was für ein Durchsatz ist möglich, wenn 10 User parallel die Suchmaschine abfragen? Um einen zu großen Einfluss des Caches zu verhindern, variieren wir dabei mit einem einfachen Random die Umkreiskoordinaten.

Nach 10000 Durchläufen kommen wir auf einen Throughput von über 700 Abfragen pro Minute mit einer durchschnittlichen Antwortzeit von 800ms. Nicht schlecht. Es gibt jedoch eine ganze Reihe an Tuning-Parametern:

- Suchparameter
- Radius-Wahl
- Berechnungsmethode (arc / plane)
- Threadanzahl
- Tile-Auflösung
- Heapgröße
- Garbage Collector Einstellungen
- Cachegröße

Gibt es die richtigen Parameter? Nope, alles hängt von der Datenmenge, den Requirements und den jeweiligen Peaksituationen
ab (oder auch Ausfallsicherheit, Aktualität der Index-Daten, Performance-Clustering, etc)

Konsequenz:
Es müssen typische Szenarien definiert werden. Welche Suchanfragen kommen häufig vor, was sind die Ausreisser
im Live-Betrieb? Was beinhaltet der Index jetzt und was in ein paar Monaten. Dann hilft nur testen, testen und tunen.
Und ständiges, kontinuierliches Prüfen der Performance (mit JMeter, JConsole, Luke, …).