Zweitägiges Elasticsearch Training in Paris (Tag 1)

von exensio

Am 16ten und 17ten September nahmen wir am »Core Elasticsearch Training« in Paris teil. Dieser  Kurs wird von der Firma Elasticsearch angeboten, und ist momentan das einzige offizielle Training.

Die Teilnehmer waren bunt gemischt aus mehreren Ländern, mit unterschiedlichen Technologien und Elasticsearch Kenntnissen. Es gab etwa 20 Teilnehmer und drei Leiter, die abwechselnd über Elasticsearch vortrugen und die man auch jederzeit fragen konnte.

Zuerst gab es eine Einführung in Elasticsearch, welches eine dokumentenorientierte Suchmaschine ist. Elasticsearch benutzt JSON Dokumente zum Transport und zum Speichern. Das heißt, die Dokumente werden im JSON-Format indiziert, und die Suchergebnisse als JSON präsentiert. Fast alles wird in Elasticsearch als API zur Verfügung gestellt. Elasticsearch benutzt zum Speichern sowie Suchen der Dokumente Apache Lucene. Apache Lucene ist eine Open Source Suchbibliothek, geschrieben in Java.

Elasticsearch benötigt kein vordefiniertes Schema, um Dokumente zu indizieren. Es bildet ein automatisches Schema aus den eingelesenen Werten. Ohne explizites Mapping schließt Elasticsearch auf den Typ des gesendeten Wertes. Beim senden von “Max” schließt Elasticsearch auf den Typ String, bei einer “1” wird automatisch ein Mapping für numerische Werte erstellt. Es wird jedoch empfohlen explizit ein Mapping anzugeben. Wer mehr über das Mapping der Typen erfahren möchte, kann das unter  https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html tun.

Elastic in Elasticsearch bezieht sich auf ein skalierbares und hochverfügbares Netzwerksystem. Es kann (und sollte) auf mehr als einem  Server  installiert werden. Somit kann sich Elasticsearch selbstständig von Netzwerksausfällen automatisch erholen. Es ist dazu ausgelegt, viele Clients schnell zu bedienen und auf die verschiedenen Knoten, auf denen Elasticsearch läuft, auszugliedern.

Uns wurde die Verzeichnisstruktur von Elasticsearch detailliert erläutert. Um Elasticsearch zu starten, benutzt man das Skript bin/elasticsearch. Um es im Vordergrund auszuführen, muss man die -f Option beim Starten setzen. Man kann Elasticsearch in der Datei config/elasticsearch.yml konfigurieren. Gut zu sehen war, das man nicht viel einstellen muss, da die meisten Einstellungen so gewählt sind, dass ein »normaler« Benutzer damit direkt loslegen kann, getreu dem »Convention over Configuration«[1] Prinzip. Es wurde jedoch empfohlen, das Daten-Verzeichnis einzustellen, damit mehrere parallel laufende Elasticsearch-Instanzen auf die gleichen Daten zugreifen können. Weitere Details zur Installation und Konfiguration kann man unter http://www.elastic.co/guide/reference/setup/installation/ und http://www.elastic.co/guide/reference/setup/configuration/ sehen.

Plugins können von einer Zip-Datei oder sogar direkt von einem Maven Repo oder Github installiert werden. Um mehr darüber zu erfahren, wie man Plugins einbindet, sowie um interessante Plugins zu sehen, sollte man sich die Seite http://www.elastic.co/guide/reference/modules/plugins/ ansehen.

Im Elasticsearch Umfeld bezieht sich Index auf den Namen der logischen Gliederung der Dokumente. Man kann es sich so wie eine Datenbank vorstellen. Um die Elasticsearch Terminologie zu verstehen, sei auf http://www.elastic.co/guide/reference/glossary/ verwiesen. Bei den Beispielen im Kurs wurde die API mit JSON über curl aufgerufen. Curl ist ein Kommandozeilenprogramm, das man als HTTP-Client benutzen kann. Es ist für fast alle Betriebssysteme verfügbar, und man kann es auf http://curl.haxx.se/ runterladen.

Um ein Dokument zu indizieren, zum Beispiel ein Tweet, kann man folgenden Aufruf machen:

curl -XPUT ‚http://localhost:9200/twitter/tweet/1‘ -d ‚{
    »user« : »kimchy«,
    »post_date« : »2009-11-15T14:12:12«,
    »message« : »trying out Elastic Search«
}‘
Da es der erste Aufruf von Elasticsearch ist, möchte ich ihn kurz erklären. Der erste Teil der URL, http://localhost, bezieht sich auf den Elasticsearch-Server, den wir benutzen; in diesem Fall localhost. Der zweite Teil der URL bezieht sich auf den Port 9200, auf dem Elasticsearch läuft.  Es wird ein PUT-Aufruf mit der internen URL “twitter/tweet/1” abgesetzt. Dabei ist »twitter« der Index, und »Tweet« das Dokument mit der ID 1. Als Antwort erhält man entweder den Status 201 (Created - wenn das Dokument neu erstellt worden ist) oder 200 (Ok - bei einer Änderung oder Reindizierung). In der Antwort erhält man den Typ des Dokuments in dem Feld »_type«, was in diesem Fall »tweet« ist. Außerdem die ID des Dokuments (im Feld _id) sowie die Version (_version). Man kann an der Rückgabe der ID erkennen, dass die ID optional ist und auch aus dem Dokument ausgelesen oder autogeneriert werden kann. Man kann mehr zur Indizierung in http://www.elasticsearch.org/guide/reference/api/index_/ erfahren.
 
Die API Anfragen können an jede Elasticsearch-Instanz geschickt werden. Eine Instanz wird auch Knoten genannt. Dieser routet beim Indizieren an den primären Shard. Ein Shard ist ein Lucene Index, der von Elasticsearch verwaltet wird. Wenn die Anzahl der Shards größer 1 ist werden die Shards auf die Knoten im Cluster verteilt. Es gibt primäre und sekundäre Shards. Die sekundären Shards werden als Replikas bezeichnet und sind zur Ausfallssicherheit gedacht. Nachdem das Dokument auf dem primären Shard indiziert worden ist, wird es auf den sekundären Shards repliziert.  Danach erhält der Client eine Antwort mit dem Ergebnis der Indizierung. Die sekundären Shards sollten sich auf einem anderen Knoten befinden. Es wurde empfohlen nur einen primären Shard eines Indizes auf einem Knoten verwalten zu lassen.
 
Nachdem ein Dokument indiziert ist, erhält man es sofort, indem man den Typen und die ID angibt:
curl -XGET ‚localhost:9200/twitter/tweet/1‘
Man kann auch nur einzelne Felder des Dokuments anfordern mit:
curl -XGET ‚localhost:9200/twitter/tweet/1?fields=user‘
Falls die Felder während des Mappings gespeichert worden sind, werden sie aus dem Index ausgelesen, und ansonsten aus dem _source Feld, welches das gesamte Dokument enthält, ausgerechnet.
 
Man kann auch nur sehen, ob es ein Dokument mit einer ID und Typ gibt mit:
curl -XHEAD 'localhost:9200/twitter/tweet/1'
Man beachte, dass es eine HEAD-Anfrage ist, und man erhält als Antwort den Status 200 wenn das Dokument existiert, und 404 wenn nicht. Es ist auch viel schneller, als das ganze Dokument zu laden.
 
Ein Dokument kann mit einer DELETE-Anfrage auch gelöscht werden, zum Beispiel:
curl -XDELETE 'localhost:9200/twitter/tweet/1'
Man erhält als Antwort den Status 404 (nicht gefunden) oder 200, und in der Antwort wird bei dem Feld »found« angegeben, ob das Dokument gefunden worden ist.
 
Wenn ein Dokument geändert wird, wird intern erst das Dokument geladen, dann werden die Änderungen angewandt und anschließend das Dokument neu gespeichert mit einer höheren Version.
curl -XPOST 'localhost:9200/twitter/tweet/1/_update' -d '{
 "doc" : {
  "message" : "Now I can update with Elasticsearch"
 }
}'
Man kann außerdem mehrere Operationen auf einmal - mit einem Bulk-Aufruf - durchführen, um den Netzwerkverkehr zu minimieren. Bulk kann man mit Masse übersetzen. Bei diesem Aufruf muss jede Aktion in einer eigenen Zeile liegen, damit Elasticsearch es versteht. Das betrifft auch die letzte Zeile. Ein Beispiel kann man hier sehen:
{ "delete" : { "_index" : "twitter", "_type" : "tweet", "_id" : "2" } }\n
{ "index" : { "_index" : "twitter", "_type" : "tweet", "_id" : "1" } }\n
{ "user" : "Tony", "message" : "Test" }\n

Bei diesem Aufruf bekommt man als Antwort das Ergebnis der einzelnen Operationen, in der gleichen Reihenfolge in dem sie aufgerufen worden sind. Man muss beachten, dass die Transaktionalität nicht garantiert ist, sprich es kann passieren, dass einige Operationen gelingen und andere nicht. Es wird empfohlen, die Ergebnisse einzeln durchgehen, um zu sehen, was mit jedem Aufruf passiert ist.


Bei einer Suche ist es möglich, den Query-Parameter direkt anzuhängen:

curl -XGET 'localhost:9200/twitter/_search?q=tony'
Dieses Vorgehen wird jedoch nicht empfohlen, insbesondere wenn es sich um kompliziertere Querys handelt, da die Query-Struktur von Lucene nicht allzu eingängig ist. Es ist einfacher eine Match-Query zu machen, die am typischen Suchbox-Benutzungsfall angelehnt ist:
curl -XPOST 'localhost:9200/_search' -d '{
 "query" : {
  "bool" : {
   "must" : [
    {
     "match" : {
      "user" : "peter paul"
     }
    },
    {
     "range" : {
      "amount" : { "gte" : 100 }
     }
    }
   ]
  }
 }
}'
Dabei wird bei beiden Termen eine OR-Query standardmäßig gemacht.
Es wurde auch empfohlen, eine Filter-Query zu machen, da man damit oft die Performanz steigert. Die Filter werden im Cache gespeichert, und bei der nächsten Query mit dem gleichen Filter zieht Elasticsearch nur die bereits vorgefilterte Dokumente in Betracht. Ein Beispiel, wie eine solche Query aussehen kann:
curl -XPOST 'localhost:9200/_search' -d '{
 "query" : {
  "filtered" : {
   "query" : {
    "match" : {
     "user" : "peter paul"
    }
   },
   "filter" : {
    "range" : {
     "amount" : { "gte" : 100 }
    }
   }
  }
 }
}
Aus Performanzgründen sollte man Boolean-Querys nehmen. Diese sind »must«, »must_not« und »should«. Must muss alle Query Terme enthalten, und ist ähnlich wie eine Und-Verknüpfung. Must not darf kein Term enthalten, und ist analog zu einer »Not« Query. Und should sollte die Query-Termen enthalten, es werden aber auch andere Dokumente gefunden, mit einer geringeren Wahrscheinlichkeit. Dies ist ähnlich wie eine Oder-Verknüpfung.
Standardmäßig werden bei einer Suche die ersten 10 Dokumente zurückgegeben. Dies kann mit den Parametern »from« und »size« beeinflussen. Im Kurs wurde empfohlen, auf diese Parameter zu achten, da insbesondere bei einem hohen »from« Wert viele Dokumente geladen werden müssen, und die Query langsam werden kann. Bei jeder Suche werden von jedem Shard die Anzahl des »from« Wertes multipliziert mit der »size« Wert und, nachdem alle Dokumente geordnet sind, nur die gewünschten Dokumente zurückgegeben.
Lucene benutzt einen inversen Index. Da wird dann in etwa zu jedem Wort das dazugehörige Dokument in den Shards gespeichert, in dem es vorkommt. Wichtig anzumerken, dass Lucene nie alte Information überschreibt, sondern immer neue Daten anhängt. In diesem Zusammenhang wurde die Empfehlung ausgesprochen, Elasticsearch auf SSD auszuführen. Diese sind zwar noch teurer als normale Festplatten, aber viel günstiger bei der Anzahl der IO-Operationen pro Sekunde (IOPS). Außerdem eignen sich SSD-Laufwerke nicht so gut wenn man oft Daten überschreiben muss, was aber in Elasticsearch nicht der Fall ist.
Wenn man ein Dokument indiziert, wird das Dokument in den einzelnen Begriffen auseinandergenommen, normalerweise nach Leerzeichen. Man sollte darüber nachdenken, ob es vielleicht sinnvoll ist gleich alle Worte in Kleinschreibung zu speichern. Damit findet man den Begriff »Auto«, wenn man nach »auto« sucht. Außerdem kann man überlegen, auch einen Stemming-Filter einzubauen. Stemming speichert nur den Wortstamm ab, sprich aus »Autos« und »Auto« speichert er nur Auto. Andere interessante Filter können ein Stoppwortfilter sein, der keine allgemeinen Wörter, wie z.B. »die« speichert. Oder ein Synonymfilter, der bei jedem der Wörter, die als Synonym auftauchen, einmal das Wort und einmal das Synonym speichert. Bei diesen Filtern sollte man beachten, dass sie besser für englische als für die deutsche Sprache funktionieren. Man sollte die Reihenfolge der Filter beachten, da sie in genau so  angewendet werden. Zum Beispiel kann es Sinn machen, den Filter zum Kleinschreiben vor dem Stoppwort Filter zu setzen, da man damit die Stoppwörter nur in Kleinschreibung angeben muss. Wichtig ist auch, die  Filter in der gleichen Reihenfolge beim Suchen zu benutzen, damit die Filter auch auf die Suchbegriffe angewandt werden.
Es wird empfohlen, die Analyzer mit Dokumenten auszuprobieren, um spätere Überraschungen auszuschließen.
Man kann dem Benutzer die Anzahl der Dokumente mit einem gewissen Wert anzeigen, zum Beispiel alle Dokumente mit einem gewissen Preis oder aus einer gewissen Stadt. Diese Art der Navigation und Suche wird bei Elasticsearch Facet genannt. Bei der Analyse sollte man darauf achten, dass man diese Werte nicht in Tokens auseinandernimmt. Ansonsten bekommt man dann bei der Stadt »Frankfurt am Main« 3 Tokens. Um dies zu erreichen, kann man ein Feld zweimal Mappen, einmal normal, in einzelne Wörter auseinandernehmen, und ein zweites Mal, mit einem anderen Namen, für die Facetsuche.

Diese waren nur ein paar Beispiele, die wir bei dem Training am ersten Tag gesehen haben. Der Kurs war vollgepackt mit Informationen, und bald geht es weiter mit dem Blog Post zum zweiten Tag.

Kategorien: Elasticsearch

Zurück