20.022013

Hierarchische Suche mittels Elasticsearch

Jeder Softwareentwickler, der sich schonmal mit Suchen und Suchalgorythmen beschäftigt hat, ist irgendwann an den Punkt gekommen, wie mache ich eine vernünftige Volltextsuche, die aber so schnell wie möglich ist? Ich zeige euch, wie sowas sehr einfach und sehr gut mit Elasticsearch möglich ist.

Sebastian hat ja am 23.01.2013 bereits einen sehr aufschlussreichen Bericht über Elasticsearch gemacht und ich würde dies gerne noch ein wenig mehr erleutern, bzw. euch erklären, wie man seine Suchen noch komplexer machen kann, bzw noch weiter definieren kann.

Stellen wir uns also mal ein Beispiel vor, wir betreiben eine Bibliothek und wollen alle unsere Bücher indizieren über Elasticsearch. Nun gibt es bei Elasticsearch mehrere Möglichkeiten, wie man dies tun kann.

  1. Man indiziert alle Bücher einzeln, mit Autorenname als eigenes Feld.
  2. Man indiziert die Autoren und macht ein Nested Object bei den Autoren und dort die Bücher alle rein
  3. Oder aber man erstellt einen Index mit verschiedenen Unterpunkten und gibt bei dem Mapping der Bücher ein _parent ein.

Ich habe mich dann für die 3. Variante hierbei entschieden, da man so sehr viel schönere Suchen in Elasticsearch gestalten kann, also mit den anderen 2 Möglichkeiten.

Was muss man hierzu beachten? Zum einen müsst, bzw könnt ihr so nun eure Autoren alle eintragen. Legen wir einfach mal zum Beispiel 2 Autoren an in unserem Index "Buecher", den wir in der Elasticsearch API angelegt haben.

curl -XPUT localhost:9200/buecher/autoren/1 -d'{ "name": "Astrid Lindgren" }'
curl -XPUT localhost:9200/buecher/autoren/2 -d'{ "name": "Wilhelm Busch" }'
curl -XPUT localhost:9200/buecher/autoren/3 -d'{ "name": "E. L. James" }'

Soweit ist das ja nicht schwer und hier bedarf es auch nicht wirklich einem besonderen Mapping. Was nun noch wichtig ist, ist aber das Mapping in Elasticsearch für die Bücher. Hier muss man den _parent mit angeben, was wie folgt geschieht:

curl -XPOST localhost:9200/buecher/buecher/_mapping -d '{
  "book":{"_parent": {"type": "autoren"}}
}'

Nachdem das Mapping in Elasticsearch eingetragen wurde, können wir die Bücher auch füllen. Hierzu muss lediglich beim Eintragen der wert ?parent=ID übergeben werden und die Bücher können zu den Autoren zugeordnet werden.
Tragen wir also doch einfach mal ein paar Bücher ein und machen ein paar schicke Suchen.

curl -XPOST localhost:9200/buecher/buecher/1?parent=1 -d '{
   "name": "Pippi Langstrumpf",
   "genre": "Kinderbuch 6-10 Jahre",
   "herausgeber": "Rabén & Sjögren"
}'

curl -XPOST localhost:9200/buecher/buecher/2?parent=1 -d '{
   "name": "Britt-Mari erleichtert ihr Herz",
   "genre": "Mädchenbücher 10-15 Jahre",
   "herausgeber": "Rabén & Sjögren"
}'

curl -XPOST localhost:9200/buecher/buecher/3?parent=2 -d '{
   "name": "Max und Moritz",
   "genre": "Kinderbuch 6-10 Jahre",
   "herausgeber": "Braun und Schneider"
}'

Nun haben wir 3 Bücher in Elasticsearch indiziert und können uns mal einer Beispielabfrage widmen. Wenn man z.B. nun alle Autoren haben möchte, die jemand Kinderbücher geschrieben haben, macht man folgendes

curl -XPOST localhost:9200/buecher/autoren/_search -d '{
  "query": {"has_child": {
      "type": "buecher",
      "query" : {"filtered": {
          "query": { "match_all": {}},
          "filter" : {"and": [{"term": {"genre": "Kinderbuch 6-10 Jahre"}}]}
        }}}}}'

Nun bekommen wir als Ergebnis die 2 Autoren, die mit Kinderbüchern eingetragen wurden. So kann man aber auch andere Abfragen gestalten, wie z.B. has_parent anstatt has_child, was man bei einer Büchersuche angeben kann. Wenn man z.B. noch zusätzliche Daten in Elasticsearch indiziert bei den Autoren, kann man sogar abfragen machen von Büchern, wo die Autoren bereits verstorben sind, oder ich möchte alle Bücher sehen, von Autoren, die jemals Kinderbücher gemacht haben.

Die Möglichkeiten sind unendlich und die Ergebnisse kommen natürlich wieder elastisch-schnell zurück. In dem Sinne, hoffe ich dass ich euch ein wenig Elasticsearch wieder näher bringen konnte und bis zum nächsten mal.

Euer Mark!