Über unsMediaKontaktImpressum
Florian Hopf 01. Dezember 2015

Aggregationen in Elasticsearch

Wer seinen Nutzern eine flexible Möglichkeit zum Zugriff auf textlastige Daten bieten will, kommt um eine spezialisierte Datenhaltung nicht herum. Eine populäre Option ist dabei der Suchserver Elasticsearch [1], der für die Datenhaltung und den Zugriff die Bibliothek Apache Lucene [2] nutzt und die Funktionaltität über HTTP-Schnittstellen zur Verfügung stellt. Neben der Volltextsuche stellt Elasticsearch mit den Aggregationen ein mächtiges Werkzeug zur Verfügung, das genutzt werden kann, um einen ganz neuen Blick auf die Daten zu bekommen. Dieser Artikel zeigt an einigen Beispielen, wie solche Aggregationen mit Elasticsearch genutzt werden können.

Dokumente

Elasticsearch speichert Daten in Form von Dokumenten. Zur Auszeichnung kommt sowohl bei der Kommunikation mit Elasticsearch als auch bei der internen Speicherung JSON zum Einsatz. Für die folgenden Beispiele stellen wir uns vor, dass wir eine Suchmaschine für Bibliotheken betreiben wollen. Darin kann nach den Metadaten von Büchern wie Autor, Titel oder Verlag gesucht werden. Um ein Buch in Elasticsearch zu speichern, kann das Dokument über einen POST-Request an Elasticsearch geschickt werden, beispielsweise über den HTTP-Kommandozeilen-Client cURL[3].

curl -XPOST "http://localhost:9200/library/book" -d'
{
    "title": "Elasticsearch - Ein praktischer Einstieg",
    "author": "Florian Hopf",
    "published": "2015-12-01T00:00:00.000Z",
    "pages": 250,
    "tags": ["Elasticsearch", "Suche"],
    "publisher": {
        "name": "dpunkt.verlag",
        "country": "DE"
    }
}'

Elasticsearch steht standardmäßig unter Port 9200 per HTTP zur Verfügung, der Rest der Url bezieht sich auf zwei weitere Aspekte der Datenspeicherung: library beschreibt den sogenannten Index – eine logische Sammlung an Dokumenten. book beschreibt den Typ des zu indizierenden Dokuments.

Neben textuellen Inhalten wie Titel und Autor enthält das Dokument noch ein Datumsfeld und einen numerischen Wert. Die Schlagworte sind in einer Liste und die Informationen zum Verlag in einem Subdokument angegeben. Ohne weiteres Zutun speichert Elasticsearch dieses Dokument und macht es über den verwalteten Index gleichzeitig durchsuchbar. Über einen einfachen Aufruf können wir auf den indizierten Daten suchen, beispielsweise nach dem Begriff elasticsearch im Titel.

curl -XPOST "http://localhost:9200/library/book/_search" -d'
{
    "query": {
        "match": {
           "title": "elasticsearch"
        }
    }
}'

Im Body des HTTP-Requests wird hier eine Match-Query übergeben, die Standardsuche für Volltextinhalte. Die Antwort enthält eine List mit den besten Treffern für den Suchbegriff inklusive des gespeicherten Originalinhalts. Durch die Basis Lucene stehen in Elasticsearch viele unterschiedliche Suchfeatures zur Verfügung, die zum Aufbau einer modernen Suchanwendung genutzt werden können. Für diesen Artikel interessiert uns jedoch eine weitere Möglichkeit, auf die Daten zuzugreifen: die Aggregationen.

Terms-Aggregationen

Die Aggregationen arbeiten auf den indizierten Daten und können uns Informationen über die Dokumente zurückgeben. Dies wird oft genutzt um eine sogenannte Facettierung aufzubauen, wie sie beispielsweise von E-Commerce-Plattformen wie Amazon oder eBay bekannt ist. Für die momentan angezeigten Ergebnisse wird – meist auf der linken Seite – eine oder mehrere dynamische Wertelisten angezeigt, anhand derer die Ergebnisliste weiter eingeschränkt werden kann.

Für unser Beispiel können wir uns vorstellen, dass wir dem Benutzer eine Liste der enthaltenen Schlagwörter der gefundenen Bücher anzeigen wollen. Dazu können wir die sogenannte Terms-Aggregation verwenden, die uns für einzelne Felder die enthaltenen Werte zurückliefern kann. Eine Aggregation kann statt oder zusätzlich zu einer Abfrage über das Schlüsselwort aggs angefordert werden.  

curl -XPOST "http://localhost:9200/library/book/_search" -d'
{
    "aggs": {
        "common-tags": {
            "terms": {
                "field": "tags"   
            }
        }
    }
}'

Wir vergeben einen Namen für unsere Aggregation – common-tags – und geben an, dass es eine Terms-Aggregation auf dem Feld tags sein soll. Elasticsearch antwortet darauf neben den Suchergebnissen mit einem Abschnitt, der die Daten für die Aggregation zurückliefert.

   "aggregations": {
      "common-tags": {
         "doc_count_error_upper_bound": 0,
         "sum_other_doc_count": 0,
         "buckets": [
            {
               "key": "elasticsearch",
               "doc_count": 2
            },
            {
               "key": "lucene",
               "doc_count": 1
            },
            {
               "key": "suche",
               "doc_count": 1
            }
         ]
      }
   }

Für jeden verfügbaren Wert wird ein sogenannter Bucket zurückgegeben. Dieser enthält den Wert, wie er im Index gespeichert ist, und die Anzahl der Dokumente, die diesen Wert enthalten. Die Rückgabe wird standardmäßig nach der Anzahl der Vorkommen sortiert und es werden maximal zehn Ergebnisse zurückgeliefert.

Wenn eine Query übergeben wird, beziehen sich die Buckets auf das Ergebnis dieser Abfrage, ansonsten werden alle Dokumente im Index ausgewertet. In einer Oberfläche können die Werte der Buckets direkt dem Benutzer angezeigt werden, ein Klick löst dann eine weitere Abfrage aus, die die Ergebnisliste auf das gewählte Schlagwort einschränkt.

Range-Aggregationen

Eine weitere häufig eingesetzte Gruppe an Aggregationen sind die Range-Aggregationen. Diese fassen Bereiche zusammen und werden meist auf numerische Felder oder Datumswerte angewandt. So können in Online-Shops Preisspannen visualisiert werden oder auf einer News-Seite Beiträge zeitlich gruppiert werden. Um die Seitenzahl der Bücher anhand von zwei Bereichen zu gruppieren, kann die folgende Abfrage verwendet werden.

curl -XPOST "http://localhost:9200/library/book/_search" -d'
{
    "aggs": {
        "pages": {
            "range": {
                "field": "pages",
                "ranges": [
                    { "to": 300 },
                    { "from": 300 }
                ]
            }
        }
    }
}'

Wir definieren zwei Bereiche in der Aggregation, einen bis 300 Seiten und einen ab 300 Seiten. Die Antwort enthält die beiden angeforderten Bereiche.

   "aggregations": {
      "pages": {
         "buckets": [
            {
               "key": "*-300.0",
               "to": 300,
               "to_as_string": "300.0",
               "doc_count": 1
            },
            {
               "key": "300.0-*",
               "from": 300,
               "from_as_string": "300.0",
               "doc_count": 1
            }
         ]
      }
   }

Für Datumswerte ist die spezialisierte Date-Range-Aggregation verfügbar, häufig wird jedoch auch ein Histogramm verwendet, das Dokumente in Buckets festgelegter Intervalle zusammenfasst. Dieses ist beispielsweise geeignet um die Bücher monatlich anhand des Erscheinungsdatums zu gruppieren. Zur Anfrage sind nur die Übergabe des Feldes und des Histogramms notwendig.

Stats-Aggregationen

Neben den bisher vorgestellten Bucket-Aggregationen, die die Dokumente in einzelne Datentöpfe sortieren, gibt es auch noch die Stats-Aggregationen, die einzelne Werte berechnen und meist auf Datums- und numerischen Feldern zum Einsatz kommen. Um beispielsweise die maximale Seitenzahl zu ermitteln, kann die max-Aggregation eingesetzt werden.

curl -XPOST "http://localhost:9200/library/book/_search" -d'
{
    "aggs": {
        "max-pages": {
            "max": {
                "field": "pages"   
            }
        }
    }
}'

Als Ergebnis erhalten wir nun keinen Bucket mehr, sondern einen einzelnen Wert.

Die bisher vorgestellten Funktionen sind bereits sehr nützlich, um Daten zu gruppieren und Werte darauf zu berechnen. Richtig mächtig werden Aggregationen jedoch, wenn sie kombiniert werden. Die Ergebnisse von einzelnen Bucket-Aggregationen können als Eingabe für weitere Aggregationen verwendet werden. Somit wird es beispielsweise möglich, die durchschnittliche Seitenzahl pro Monat der Veröffentlichung oder pro Schlagwort zu ermitteln.

Um eine solche Anfrage abzusetzen, wird eine weitere Aggregation auf Ebene einer bestehenden angefordert.

curl -XPOST "http://localhost:9200/library/book/_search" -d'
{
   "aggs": {
      "common-tags": {
         "terms": {
            "field": "tags"
         },
         "aggs": {
            "max-pages": {
               "max": {
                  "field": "pages"
               }
            }
         }
      }
   }
}'

Für jeden Bucket der ersten Aggregation wird eine zweite Aggregation berechnet. So ergeben sich mehrstufige Gruppierungen.

   "aggregations": {
      "common-tags": {
         "doc_count_error_upper_bound": 0,
         "sum_other_doc_count": 0,
         "buckets": [
            {
               "key": "elasticsearch",
               "doc_count": 2,
               "max-pages": {
                  "value": 400
               }
            },
            {
               "key": "suche",
               "doc_count": 1,
               "max-pages": {
                  "value": 250
               }
            }
         ]
      }
   }

Die Anwendungsfälle für solche Abfragen sind vielfältig, beispielsweise kann der beliebteste Hashtag pro Stunde aus Social Media-Daten ermittelt werden oder in einem Online-Shop der durchschnittliche Warenkorbwert der Bestellungen pro Tag. Eine Visualisierung solcher Werte, beispielsweise über die Oberfläche Kibana[4], kann helfen, bessere Einblicke in die anfallenden Daten zu erhalten.

Weitere Aggregationen

Mit den Aggregationen bietet Elasticsearch ein mächtiges Werkzeug, das sowohl für Suchanwendungen als auch für Analytics genutzt werden kann. Auch bei großen Datenmengen können die unterschiedlichen Möglichkeiten Einblicke bieten, die bei einfachen Abfragen verloren gehen können. Die Entwickler an Elasticsearch arbeiten an immer neuen Aggregationen oder Erweiterungen, die eingebunden werden können. In der kürzlich veröffentlichten Version 2.0 ist beispielsweise mit den Pipeline-Aggregationen [5] ein Feature hinzugekommen, mit dem die Ergebnisse von Aggregationen weiter verarbeitet werden können.

Neben den hier vorgestellten Aggregationen existieren noch einige weitere, die für unterschiedliche Einsatzfelder nützlich sind. Für indizierte Geodaten können beispielsweise Aggregationen für Bereiche aufgespannt werden – nützlich für Kleinanzeigenmärkte oder Location-Based-Services. Die Significant-Terms-Aggregation[6] verhält sich ähnlich zur Terms-Aggregation, liefert allerdings nur die Terme zurück, die eine bestimmte Dokumenten-Menge vom Rest der Dokumente unterscheidet. Damit können unterschiedliche Anwendungen bedient werden, von der Empfehlung ähnlicher Produkte bis zum Aufdecken von Kreditkartenbetrug.

Für das weitere Verständnis wird zum Schluss noch auf das Zusammenspiel der Aggregationen mit dem invertierten Index eingegangen.

Aggregationen und der invertierte Index

Wer beim Beispiel der Terms-Aggregationen gut aufgepasst hat, wird gemerkt haben, dass die Schlagwörter klein geschrieben zurückgeliefert wurden, obwohl diese im Originaldokument groß geschrieben waren. Um zu verstehen, warum das so ist, müssen wir einen genaueren Blick auf den internen Aufbau des Index werfen und wie wir diesen beeinflussen können.

Die Suche in Elasticsearch funktioniert über einen sogenannten invertierten Index. Dabei werden aus den textuellen Inhalten einzelne Bestandteile, die sogenannten Terme, extrahiert, die dann den Dokumenten zugeordnet werden, in denen sie vorkommen. Um zu entscheiden, welche Terme aus einem Text gebildet werden, wird bei der Speicherung der Dokumente der sogenannte Analyzing-Prozess durchgeführt.

Für zwei unterschiedliche Dokumente könnte der Index für die Schlagwörter wie in der folgenden Tabelle aussehen.

Term Dokument
elasticsearch 1,2
suche 2

Die einzelnen Schlagwörter bilden die Terme. Diese sind auf die Dokumente abgebildet, in denen sie vorkommen. Eine Suche entspricht dann nur noch einem Lookup in dieser Struktur und kann damit sehr schnell durchgeführt werden. Dieses Verhalten entspricht dem Stichwortverzeichnis in einem Buch, bei dem wichtige Begriffe auf die Seitenzahlen abgebildet sind, in denen sie vorkommen.

Wirklich nützlich wird die Nutzung des invertierten Index für Suchlösungen dadurch, dass die einzelnen Terme im Index so vorbereitet werden, dass unterschiedliche Suchen vom Nutzer bedient werden können. Standardmäßig werden beispielsweise die Terme in Kleinbuchstaben überführt, damit es für Nutzer keinen Unterschied mehr macht, ob eine Suche in Groß- oder Kleinschreibung durchgeführt wird oder in welcher Schreibweise ein Begriff im Text aufgetaucht ist.

Die Terms-Aggregation arbeitet nun, wie der Name es schon andeutet, direkt auf den Termen im Index. Weil standardmäßig eine Überführung in Kleinbuchstaben durchgeführt wird, sind unsere Ergebnisse in Kleinbuchstaben umgewandelt. Selbst wenn das nicht so schlimm ist, werden auch noch weitere Probleme auftreten. Wenn wir Schlagwörter hinzufügen, die Leerzeichen enthalten, werden diese durch den Analyzing-Prozess an den Leerzeichen aufgespalten und es bilden sich ungewollt mehrere Terme, die dann durch die Aggregation einzeln zurückgeliefert werden. Um dies zu vermeiden müssen wir den Analyzing-Prozess über das sogenannte Mapping beeinflussen.

Wie wir zu Beginn gesehen haben, mussten wir über die URL einen Typ für unsere indizierten Dokumente vergeben. Dieser Typ entscheidet über sein Mapping darüber, wie einzelne Felder im Dokument gespeichert werden. Standardmäßig wird jedes Textfeld mit dem Standard-Analyzer verarbeitet, der gut für die Suche in westlichen Sprachen geeignet ist, für Aggregationen jedoch zu viele Veränderungen vornimmt.

Damit die Suche auf dem tags-Feld wie gehabt funktioniert, können wir Elasticsearch anweisen, ein weiteres Feld zum Index hinzuzufügen, das wir dann für die Aggregationen verwenden können. Im folgenden fügen wir ein Unterfeld tags.raw hinzu, das von keinem Analyzer verarbeitet wird und deshalb wie es ist in den Index geschrieben wird.

curl -XPUT "http://localhost:9200/library/book/_mapping" -d'
{
    "book": {
        "properties": {
            "tags": {
                "type": "string",
                "fields": {
                    "raw": {
                        "type": "string",
                        "index": "not_analyzed"
                    }
                }
            }
        }   
    }
}'

Damit wir das Feld nutzen können, müssen wir die Dokumente erneut indizieren, da erst dann die Inhalte befüllt werden. Anschließend können wir über Angabe des Feldnamens tags.raw in der Terms-Aggregation die Originalinhalte anfordern.

Autor

Florian Hopf

Florian Hopf arbeitet als freiberuflicher Softwareentwickler in Karlsruhe. Er ist verantwortlich für kleine und große Suchlösungen, im Intranet und Internet, für Web-Inhalte und anwendungsspezifische Daten, basierend auf Lucene,...
>> Weiterlesen
Buch des Autors:

botMessage_toctoc_comments_9210