23.012013

Suchen mit Elasticsearch

Hey,

wie ihr sicher schon bemerkt habt, habe ich es mir zur Aufgabe gemacht, für gute Performance verschiedener Anwendungen zu sorgen...

Dazu gehört auch das Entlasten verschiedener Systeme (siehe mein Beitrag zum Caching) oder dafür zu sorgen, dass einzelne Dienste wirklich nur dazu verwendet werden, wofür sie eigentlich gedacht sind.

Und das bringt mich auch schon zum Thema, eine Datenbank ist sicherlich nicht primär dafür gedacht ständig komplexe Suchanfragen zu bearbeiten. Hierfuer gibt es weitaus geeignetere Dienste, bspw. Elasticsearch.

 Was ist eigentlich dieses Elasticsearch und was macht es so cool?

Elasticsearch ist ein schemaloser, RESTfulSuchservice, welcher beliebig skaliert und einfach einfach ist, das zeigt schon die Installation.

Einfach nach dem Entpacken in's Verzeichnis wechseln und folgendes ausfuehren:

bin/elasticsearch

Und schon laeuft eure erste Elasticsearchinstanz.

Unter

http://127.0.0.1:9200/_stats?pretty=true

Könnt ihr nun den aktuellen Status sehen.

Wie ist nun Elasticsearch aufgebaut? Die Dokumente werden in Indizes indiziert.
Man kann beliebig viele Indizes anlegen. Z.B. weil man seine indizierten Daten logisch trennen will oder ein Index ganz andere Settings hat als ein anderer (bspw. Replikation)

Für jeden Index können beliebig viele Mappings angelegt werden, jedes indizierte Dokument wird dann anhand der angelegten Mappingdefinition indiziert und durchsuchbar gemacht.
Hier ein kleines Beispiel:

curl -XPUT 'http://127.0.0.1:9200/users/test/_mapping' -d '
 {
  "test" : {
    "properties" : {
      "user_name" : { "type" : "string", "store" : "no", "index" : "not_analyzed" },      "location" : { "type" : "geo_point", "store" : "no"},      "geburtsdatum" : { "type" : "date", "format" : "YYYY-mm-dd", "store" : "no", "index" : "not_analyzed" }
    }
  }
}'

Damit legen wir fest, dass wir für den Index "users" ein Mapping "test" anlegen, womit wir user_name, geburtsdatum und die location (latitude,longitude) eines users indizieren wollen. Das geht dann so:

curl -XPUT localhost:9200/users/test/500 -d '{
 "user_name": "joocom", 
  "geburtsdatum": "2000-01-01",
 "location":"52.3423,9.71779"
}'

Damit haben wir nun einen User mit dem Namen joocom, Geburtsdatum 01.0.1.2000 und den passenden Geokoordinaten indiziert. Durch das vorher angelegte Mapping weiß Elasticsearch, dass es sich bei "geburtsdatum" um ein Datum und bei "location" um latitude.longitude handelt. Das ist wichtig, damit man entsprechende Suchen, wie zum Beispiel Umkreissuchen, Datumssuchen etc., ausfueheren kann.

Gott sei Dank (bzw. REST sei dank) koennen wir uns das Indizierte Dokument ganz einfach ansehen:

http://127.0.0.1:9200/users/default/500?pretty=true
{ "_index" : "users", "_type" : "default", "_id" : "500", "_version" : 1, "exists" : true, "_source" : {"user_name": "joocom", "geburtsdatum": "2000-01-01", "location":"52.3423,9.71779"} }

Nun suchen wir mal die joocom im Umkreis von 1 km um den Kröpcke in Hannover:

curl -XGET localhost:9200/users/test/_search -d '
{
 "filter":{
    "geo_distance":{
 "distance":"1km",
     "location":{
 "lat":"52.3743987","lon":"9.7385937"
      }
    }
  }
}'
{
 "took":8, "timed_out":false,
 "_shards":{
 "total":5,
 "successful":5,
 "failed":0
 },
 "hits":{
 "total":0,
 "max_score":null,
 "hits":[]
 }
} 

Keine Hits - komisch :) erweitern wir den Umkreis auf 5km:

curl -XGET localhost:9200/users/test/_search -d '
  {
 "filter":{
      "geo_distance":{
 "distance":"5km",
        "location":{
 "lat":"52.3743987","lon":"9.7385937" }
 }
 }
 }'
{
 "took": 1, "timed_out": false,
 "_shards": {
   "total": 5,
 "successful": 5,
   "failed": 0
 },
 "hits": {
   "total": 1,
 "max_score": 1.0,
   "hits": [ {
 "_index": "users", "_type": "test",
     "_id": "500",
 "_score": 1.0,
     "_source": {
 "user_name": "joocom",
       "geburtsdatum": "2000-01-01",
 "location": "52.3423,9.71779"
     }
   }]
 }
}

Ahhh - da isse ja! ;-)

Wie ihr seht, ist es wirklich sehr einfach Elasticsearch aufzusetzen, einen Index + Mapping zu erstellen, Daten zu indizieren und hinterher eine Umkreissuche auszufuehren.
Uebrigens, der Wert "took" im Response sind Millisekunden! Dass es bei nur einem Eintrag sehr schnell geht ist klar, aber ich kann euch sagen, bei nem Index mit 3,5G größe und > 2,5Mio Eintraegen dauert die gleiche Abfrage 66ms ! ;-)

Dass ich mit diesem Beitrag nur an der Oberfläche von Elasticsearch gekratzt habe und die Konfiguration auch viel Umfangreicher sein kann (Replikation, Clustering etc.) ist mir bewusst. Aber ich denke für einen ersten Eindruck ist das schon ganz gut...

Viel Spaß beim Ausprobieren
Sebastian