Elasticsearch als NoSQL-Datenbank

AKTUALISIERUNG: In diesem Artikel wird für unser gehostetes Elasticsearch-Angebot noch sein früherer Name „Found“ verwendet. „Found“ heißt heute „Elastic Cloud“. Sie können Elasticsearch auf Elastic Cloud 14 Tage lang kostenlos ausprobieren.

Kann Elasticsearch als „NoSQL“-Datenbank eingesetzt werden? Der Begriff „NoSQL“ hat je nach Kontext unterschiedliche Bedeutungen und zudem kurioserweise nicht wirklich etwas mit SQL zu tun. Unsere Antwort auf die Frage ist ein herzhaftes „vielleicht!“. Um das näher zu erläutern, sehen wir uns zunächst die verschiedenen Eigenschaften von Elasticsearch an und werfen einen Blick auf die Kompromisse, die wir eingehen mussten, um Elasticsearch zu einer der flexibelsten, skalierbarsten und performantesten Such- und Analyse-Engines zu machen.

Was ist eigentlich eine „NoSQL-Datenbank“?

NoSQL-Database.org definiert NoSQL als „Datenbanken der nächsten Generation, auf die im Wesentlichen einige der folgenden Punkte zutreffen: nicht-relational, verteilt, Open Source und horizontal skalierbar.“ Wie Sie sehen, ist das keine sehr präzise Definition.

Es geht auch nicht um SQL im Besonderen. So ist zum Beispiel die Abfragesprache für Hive klar von SQL inspiriert, wie auch die Abfragesprache für Esper, die statt mit Relationen mit Streams arbeitet. Und wussten Sie schon, dass PostgreSQL ursprünglich „Postgres“ hieß und die zugehörige Abfragesprache „Quel“ war? Was einmal vor allem als ORDBMS begann, hat heute viele Funktionen, die den Einsatz als schemaloser Dokumentspeicher ermöglichen.

Auch die ACID-Fähigkeit spielt hier keine Rolle. Ein Beispiel für eine NoSQL-Datenbank, deren Ziel es ist, ACID-Transaktionen bereitzustellen, ist Hyperdex. Und MySQL – unzweifelhaft eine SQL-Datenbank – hat sich in der Vergangenheit mit einer Reihe von zweifelhaften Auslegungen von ACID hervorgetan.

Relationen? Während die meisten NoSQL-Datenbanken keine Unterstützung für das Verbinden von Tabellen („Joining“) in dem Sinne bieten, wie es bei herkömmlichen relationalen Datenbanken gang und gäbe ist, und die Umsetzung den eigentlichen Nutzerinnen und Nutzern überlassen, gibt es doch auch hier ein paar Ausnahmen, wie beispielsweise RethinkDB, Hive und Pig. Neo4j, eine Graphdatenbank, weiß mit Relationen umzugehen und ist besonders gut darin, Relationen (Kanten) in Graphen zu durchlaufen. Bei Elasticsearch gibt es das Konzept des Verbindens beim Abfragen mit Eltern/Kind-Relationen und des Verbindens beim Indexieren mit verschachtelten Typen.

Verteilt? Auch wenn es ein paar verteilte SQL-Datenbanken und einige wenige Projekte gibt, die so etwas wie ein NoSQLite darstellen, sind neuere Datenbanken in der Regel in irgendeiner Form verteilt konzipiert.

Zusammenfassend lässt sich sagen, dass es weder viel bringt, NoSQL präzise zu definieren, noch einfach zu postulieren, dass Elasticsearch eine „Dokumentspeicher“-artige NoSQL-Datenbank sei. Während ich das hier schreibe, werden bei nosql-database.org mehr als 20 davon aufgeführt.

In den folgenden Abschnitten sehen wir uns einige wichtige Eigenschaften an und untersuchen, wie Elasticsearch diese implementiert (oder eben nicht).

Keine Transaktionen

Lucene, das die Grundlage für Elasticsearch bildet, arbeitet mit Transaktionen. Bei Elasticsearch kommen jedoch keine Transaktionen im herkömmlichen Sinne zum Einsatz. Es gibt keine Möglichkeit für ein „Rollback“ eines einmal gesendeten Dokuments, und es ist auch nicht möglich, eine Gruppe von Dokumenten zu senden und entweder alle oder keines von ihnen indexieren zu lassen. Es gibt jedoch ein „Write-ahead-Log“, das für die Dauerhaftigkeit der Operationen sorgt, ohne dass dafür ein aufwendiger Lucene-Commit nötig wäre. Außerdem lässt sich das Konsistenzlevel der Indexoperationen festlegen, indem angegeben wird, wie viele Replikas die Operation quittieren müssen, bevor etwas zurückgegeben wird. Standardmäßig ist dies ein Quorum, das heißt \(\lfloor\frac{n}{2}\rfloor + 1\).

Die Sichtbarkeit von Änderungen wird über die Indexaktualisierung gesteuert, die standardmäßig sekündlich und Shard für Shard stattfindet.

Die optimistische Gleichzeitigkeitssteuerung erfolgt durch Angabe der Version der gesendeten Dokumente.

Elasticsearch ist geschwindigkeitsorientiert. Die Verarbeitung verteilter Transaktionen ist sehr aufwendig. Wenn diese Transaktionen wegfallen, wird vieles viel einfacher. Als Gegenleistung dafür, dass wir akzeptieren, dass das, was wir lesen, mitunter nicht dem allerneuesten Stand entspricht, und dass alle denselben Zeitraum sehen, kann Elasticsearch vieles aus Caches bereitstellen – und so die schwindelerregende Performance bieten, für die wir Elasticsearch lieben.

Schemaflexibel

Bei Elasticsearch ist es nicht erforderlich, vorab ein Schema anzugeben. Wenn Sie Elasticsearch ein JSON-Dokument geben, versucht es, eigenständig den Dokumenttyp zu erkennen. Bei numerischen und booleschen Ausdrücken sowie bei Zeitstempeln funktioniert das sehr gut. Bei Zeichenfolgen (Strings) kommt der „Standard“-Analyzer zum Einsatz, der in der Regel gute Ausgangsergebnisse liefert.

Natürlich ist Elasticsearch in einem gewissen Sinne „schemafrei“, weil kein Schema angegeben werden muss, aber wir ziehen den Begriff „schemaflexibel“ vor. Für wirklich gute Suche- und/oder Analytics-Anwendungen kommt man nicht umhin, die Schemas entsprechend anzupassen. Elasticsearch verfügt zu diesem Zweck über ein umfangreiches Arsenal an leistungsfähigen Tools, wie dynamische Vorlagen, Mehrfeldobjekte und mehr. Das wird ausführlicher in unserem Artikel zum Thema „Mapping“ besprochen.

Relationen und Constraints

Elasticsearch ist eine dokumentorientierte Datenbank. Der gesamte Objektgraph, der durchsucht werden soll, muss indexiert worden sein. Das heißt, dass Ihre Dokumente vor dem Indexieren denormalisiert werden müssen. Die Denormalisierung sorgt zwar für eine höhere Abfragegeschwindigkeit (denn das Verbinden beim Abfragen entfällt), erhöht aber den Platzbedarf (da Daten mehrfach gespeichert werden müssen) und erschwert es, Daten konsistent und aktuell zu halten (da jede Änderung auf alle Instanzen angewendet werden muss). Dieser Ansatz eignet sich aber ausgezeichnet für „Write-once-read-many“-Workloads.

Nehmen wir zum Beispiel an, Sie haben eine Datenbank mit Kunden, Bestellungen und Produkten und Sie möchten anhand des Namens eines Produkts und eines Kunden nach Bestellungen suchen. Dies ließe sich lösen, indem die Bestellungen mit allen erforderlichen Informationen über den Kunden und die Produkte indexiert werden. Die Daten können dann unkompliziert durchsucht werden. Aber was, wenn Sie den Namen des Produkts ändern möchten? In einem relationalen Design mit richtiger Normalisierung würde es reichen, einfach das Produkt zu aktualisieren – voilà! Hier können relationale Datenbanken ihre Vorteile voll ausspielen. In einer Datenbank mit denormalisierten Dokumenten müsste jedoch jede Bestellung, die dieses Produkt enthält, einzeln aktualisiert werden.

Mit anderen Worten: Bei dokumentorientierten Datenbanken, wie Elasticsearch eine ist, gestalten wir unsere Mappings und speichern wir unsere Dokumente so, dass sie leichter gefunden und abgerufen werden können.

Wie bereits oben erwähnt, gibt es bei Elasticsearch das Konzept des Verbindens beim Abfragen mit Eltern/Kind-Relationen und des Verbindens beim Indexieren mit verschachtelten Typen. Darauf werden wir wahrscheinlich in einem späteren Artikel genauer eingehen. Bis dahin empfehlen wir Martijn van Groningens Präsentation „Document relations with Elasticsearch“.

Die meisten relationalen Datenbanken ermöglichen auch die Angaben von Constraints, um definieren zu können, was als konsistent zu betrachten ist und was nicht. So können Sie beispielsweise die referenzielle Integrität und Eindeutigkeit zu einer Zwangsbedingung machen und unter anderem festlegen, dass die Summe der Kontobewegungen positiv sein muss. Bei dokumentorientierten Datenbanken ist dies in der Regel nicht der Fall; Elasticsearch bildet da keine Ausnahme.

Robustheit

Eine Datenbank sollte robust sein, vor allem dann, wenn sie als maßgebliches Datensystem dient. Im Idealfall sollte es möglich sein, eine kostenaufwendige Abfrage abzubrechen, und die Datenbank darf nicht einfach so und unaufgefordert den Dienst einstellen.

Leider gibt es bei Elasticsearch (und den Komponenten, aus denen es besteht) derzeit noch Defizite beim Umgang mit OutOfMemory-Fehlern. Mehr zu diesem Thema finden Sie unter „Elasticsearch in Produktionsumgebungen, Abstürze wegen fehlendem Arbeitsspeicher“. Es ist sehr wichtig, Elasticsearch genügend Arbeitsspeicher zur Verfügung zu stellen und bei Suchanfragen mit unbekannten Arbeitsspeicheranforderungen in einem Produktions-Cluster Vorsicht walten zu lassen.

Das wird sich sicherlich im Laufe der Zeit bessern, aber Sie sollten bedenken, dass Elasticsearch geschwindigkeitsorientiert konzipiert ist und davon ausgegangen wird, dass der Arbeitsspeicher großzügig bemessen ist.

Verteilt

Siehe auch „Elasticsearch in Produktionsumgebungen, Netzwerke“.

Bevor Shay Banon Elasticsearch auf die Beine gestellt hat, hatte er an Compass gearbeitet. Da er irgendwann verstand, wie kompliziert es werden würde, Compass in eine verteilte Suchmaschine zu verwandeln, begann er ganz von vorn und entwickelte Elasticsearch1. Elasticsearch ist schon durch sein Design verteilt und einfach ausskalierbar, um auch mit sehr großen Datenbeständen auf Commodity-Hardware klarzukommen.

Für ein verteiltes System ist das Produkt unglaublich einfach zu bedienen und zu erlernen – dennoch sind verteilte Systeme inhärent kompliziert. Wir besprechen das ausführlicher in „Elasticsearch in Produktionsumgebungen, Netzwerke“ und geben hier nur einen kurzen Überblick.

Bei verteilten Systemen kann von Natur aus Etliches schieflaufen. Jedes Datenbanksystem hat seine eigenen Stärken, auf die es sich konzentriert: Einige streben nach starken Garantien, andere nach ständiger Verfügbarkeit, auch wenn das streckenweise (oder sogar meistens) mit erhöhter Fehleranfälligkeit verbunden ist. Hinzu kommt, dass das, was ein Datenbanksystem vorgibt, erreichen zu können, selten das ist, womit es tatsächlich zu tun hat, wie Kyle Kingsbury in seiner exzellenten Artikelreihe zu den Tücken von Netzwerkpartitionen dargelegt hat und in der er, kurz gesagt, feststellt, dass verteilte Datenbanken zwar bei gutem Wetter gut funktionieren, aber meistens ins Straucheln kommen, wenn das viele, was schiefgehen kann, auch wirklich schiefgeht.

Aus Sicht der Konsistenz, Verfügbarkeit und Partitionstoleranz ist Elasticsearch ein CP-System, legt man einen recht schwachen Maßstab für „konsistent“ an. Bei einer Read-only-Workload ermöglicht Elasticsearch AP-Verhalten, indem es die Mindestzahl von Master-Knoten lockerer handhabt, das heißt, es wird kein Quorum gefordert. In der Regel muss jedoch die Mehrzahl der Knoten im Cluster verfügbar sein. Werden Daten ohne diese Mehrheit in einen fehlerhaft konfigurierten Cluster – einen „Split-Brain“-Cluster – geschrieben, können die Daten unwiederbringlich verloren sein. Damit steht Elasticsearch in keiner Weise allein da.

Aus Skalierungssicht ist ein Index in eine oder mehrere Shards aufgeteilt. Diese Aufteilung erfolgt bei der Indexerstellung und kann nachträglich nicht mehr geändert werden. Das bedeutet, dass bei der Festlegung der Shards das voraussichtliche Wachstum berücksichtigt werden muss. Wenn dem Elasticsearch-Cluster dann weitere Knoten hinzugefügt werden, werden diese automatisch neu zugeordnet und umgeschichtet. Das funktioniert recht gut. Elasticsearch ist also sehr einfach ausskalierbar.

Security

Siehe auch „Elasticsearch in Produktionsumgebungen, Security“.

Elasticsearch verfügt über keinerlei Funktionen zur Authentifizierung oder Autorisierung. Sie sollten davon ausgehen, dass jede oder jeder, der eine Verbindung mit Ihrem Elasticsearch-Cluster herstellen kann, „Superuser“-Rechte hat, vor allem dann, wenn die leistungsfähigen Scripting-Funktionen von Elasticsearch aktiviert sind.

Zusammenfassung

Es ist zweifelsohne möglich, Elasticsearch als primären Speicher zu verwenden, wenn man mit den beschriebenen Einschränkungen leben kann. Ein gutes Beispiel wäre die Nutzung von Logstash. Logstash ist ein fantastisches Tool für die Verwaltung von Logdaten und für das Importieren dieser Daten in Elasticsearch oder auch das Archivieren an einem anderen Ort, falls erforderlich. Logdaten werden einmal geschrieben und häufig gelesen („Write once, read-many“). Sie werden nicht aktualisiert und es gibt weder Bedarf für Transaktionen noch für Integritäts-Constraints und dergleichen.

Wie steht es aber mit Systemen wie Postgres, die eine Volltextsuche und ACID-Transaktionen mit sich bringen? (Andere Beispiele wären die Volltextfunktionen von MySQL, MongoDB, Riak und so weiter) Mit Postgres lassen sich zwar grundlegende Suchfunktionen implementieren, aber die Lücke bei der möglichen Performance und bei den Funktionen ist gewaltig. Wie im Abschnitt zu Transaktionen erwähnt, kann Elasticsearch „schummeln“ und ziemlich viel Caching betreiben, ohne dass dabei so etwas Kompliziertes wie beispielsweise die Steuerung der Gleichzeitigkeit mehrerer Versionen eine Rolle spielt. Die Suche beinhaltet mehr als das Finden eines bestimmten Suchbegriffs in einem Text: Es geht auch darum, domänenspezifisches Wissen anzuwenden, um gute Relevanzmodelle zu implementieren, einen Überblick über die Gesamtheit der Ergebnisse zu geben und Dinge wie Rechtschreibprüfung und automatische Vervollständigung durchzuführen. Und das alles schnell.

Elasticsearch wird in der Regel zusätzlich zu einer anderen Datenbank verwendet. Der Master-Datensatz ist in einem Datenbanksystem mit stärkerem Fokus auf Constraints, Korrektheit und Robustheit sowie auf einfache transaktionale Aktualisierbarkeit gespeichert und wird asynchron an Elasticsearch „gepusht“. (Oder „gepullt“, wenn Sie mit einem der „Rivers“ von Elasticsearch arbeiten.) Darauf, wie das Ganze synchronisiert gehalten werden kann, werden wir in einem künftigen Artikel näher eingehen. Hier bei Found nutzen wir in der Regel PostgreSQL und ZooKeeper als Keeper of Truths, die wir in Elasticsearch einspeisen, um ein gutes Sucherlebnis zu ermöglichen.

Wie überall im Leben gibt es aber nicht die eine Lösung, die für alles passt, nicht die eine Datenbank, die alle anderen überflüssig macht. Da sich daran wohl nie etwas ändern wird, sollten Sie die Stärken und Schwächen Ihrer Datenspeicher genau kennen!

Referenzen

Banon, Shay: The Future of Compass & Elasticsearchhttps://thedudeabides.com/articles/the_future_of_compass


  1. Shay Banon, The Future of Compass & Elasticsearchhttps://thedudeabides.com/articles/the_future_of_compass.