Engineering

Wie viele Shards brauche ich in meinem Elasticsearch-Cluster?

Hinweis: Die Faustregel, dass Sie nicht mehr als 20 Shards pro GB Heap-Speicher haben sollten, gilt seit Version 8.3 nicht mehr. Dieser Blogpost wurde an die neue Empfehlung angepasst.

Elasticsearch ist eine sehr vielseitige Plattform, die zahlreiche Anwendungsfälle unterstützt und in puncto Datenorganisations- und Replikationsstrategien sehr viel Flexibilität bietet. Diese Flexibilität macht es aber nicht immer einfach, vorab festzulegen, wie Daten am besten in Indizes und Shards organisiert werden sollten, ganz besonders, wenn Sie mit dem Elastic Stack noch nicht so sehr vertraut sind. Suboptimale Festlegungen führen zweifellos nicht zwingend und auf der Stelle zu Problemen, aber die im Laufe der Zeit immer größer werdenden Datenmengen können irgendwann Performance-Probleme nach sich ziehen. Je mehr Daten sich im Cluster befinden, desto schwieriger wird es auch, das Problem in den Griff zu bekommen, zumal eine Korrektur die Neuindexierung einer großen Menge an Daten nach sich ziehen kann.

Wenn wir uns die Probleme von Nutzern ansehen, bei denen die Performance nicht stimmt, stellen wir in vielen Fällen fest, dass sich diese Probleme auf die Art und Weise zurückführen lassen, wie Daten indexiert worden sind und wie viele Shards sich im Cluster befinden. Dies gilt besonders für Anwendungsfälle, in denen es mehrere Mandanten gibt und/oder in denen mit zeitbasierten Indizes gearbeitet wird. Bei unseren Diskussionen mit Nutzern zu diesem Thema – sowohl im persönlichen Gespräch als auch über unser Forum – hören wir immer wieder die beiden Fragen „Wie viele Shards brauche ich?“ und „Wie groß sollen meine Shards sein?“.

Dieser Blogpost soll Ihnen dabei helfen, Antworten auf diese Fragen zu finden. Außerdem geben wir hier auch praktische Informationen zu Anwendungsfällen, in denen zeitbasierte Indizes, wie Logging- oder Security Analytics-Indizes, an einem einzigen Ort verwendet werden.

Was ist eine Shard?

Bevor wir richtig loslegen können, müssen wir uns zunächst über einige Fakten und Begriffe verständigen, die im Weiteren vorkommen werden.

Die Daten in Elasticsearch sind in Indizes organisiert. Jeder Index besteht aus einer oder mehreren Shards. Eine Shard wiederum ist eine Instanz eines Lucene-Index. Sie können sie sich wie eine selbstständige Suchmaschine vorstellen, die Abfragen nach einem Teilsatz von Daten in einem Elasticsearch-Cluster indexiert und verarbeitet.

Die in eine Shard geschriebenen Daten werden in regelmäßigen Abständen in neuen, nicht mehr veränderbaren Lucene-Segmenten auf einem Datenträger veröffentlicht und damit für Abfragen verfügbar gemacht. Dieser Vorgang wird als „Refresh“ oder „Aktualisieren“ bezeichnet. Eine ausführliche Beschreibung der Funktionsweise finden Sie in „Elasticsearch: The Definitive Guide“.

Mit wachsender Zahl von Segmenten werden diese in regelmäßigen Abständen zu größeren Segmenten zusammengefasst. Dieser Vorgang wird als „Merging“ oder „Zusammenführen“ bezeichnet. Aus der Tatsache, dass alle Segmente unveränderlich sind, ergibt sich, dass sich die Größe des genutzten Speicherplatzes während des Indexierens in der Regel ständig verändert: Neue, zusammengeführte Segmente müssen erstellt werden, bevor diejenigen, an deren Stelle sie treten, gelöscht werden können. Das Zusammenführen kann recht ressourcenintensiv sein, besonders hinsichtlich der Platten-I/O-Anforderungen.

Die Shard ist die Einheit, über die Elasticsearch Daten im Cluster verteilt. Mit welcher Geschwindigkeit Shards beim Umverteilen von Daten, z. B. im Anschluss an einen Plattenausfall, verteilt werden können, hängt von der Größe und der Zahl der Shards sowie der Leistungsfähigkeit von Netzwerk und Speichermedium ab.

TIPP: Lassen Sie Shards nicht zu groß werden. Sehr große Shards können Schwierigkeiten bereiten, wenn Daten nach einem Ausfall wiederhergestellt werden müssen. Es gibt zwar keine festen Obergrenzen für die Größe von Shards, aber man liest des Öfteren, dass Shards bis zu einer Größe von 50 GB in einer Reihe von Anwendungsfällen noch als gut beherrschbar gelten.

Indexieren nach Aufbewahrungszeit

Da Segmente unveränderlich sind, bedeutet das Aktualisieren eines Dokuments, dass Elasticsearch zunächst das existierende Dokument finden, es dann als gelöscht kennzeichnen und anschließend die aktualisierte Version hinzufügen muss. Auch beim Löschen eines Dokuments muss das Dokument zunächst gefunden und dann als gelöscht gekennzeichnet werden. Das heißt, dass gelöschte Dokumente weiterhin Speicherplatz belegen und einen gewissen Teil der Systemressourcen nutzen, bis sie zusammengeführt werden. Dabei können die Systemressourcen recht stark beansprucht werden.

In Elasticsearch lassen sich komplette Indizes sehr effizient direkt aus dem Dateisystem löschen, ohne dass alle Datensätze explizit und einzeln gelöscht werden müssen. Dies ist die bei Weitem effizienteste Methode, Daten aus Elasticsearch zu löschen.


TIPP: Versuchen Sie nach Möglichkeit, für die Datenaufbewahrungsverwaltung zeitbasierte Indizes zu verwenden. Gruppieren Sie Daten anhand der Aufbewahrungszeit in Indizes. Mit zeitbasierten Indizes ist es bei Bedarf auch leichter, die Zahl der primären Shards und Replicas zu verändern, denn dies kann für den nächsten zu generierenden Index festgelegt werden. Das vereinfacht die Anpassung an sich verändernde Datenmengen und Anforderungen.


Sind Indizes und Shards nicht für umsonst zu haben?

Für jeden Elasticsearch-Index werden im Cluster-State Angaben zu Mappings und Zustand gespeichert. Diese Angaben werden im Hauptspeicher aufbewahrt, damit sie schneller abgerufen werden können. Große Mengen von Indizes und Shards in einem Cluster können daher zu einem großen Cluster-State führen, besonders wenn die Mappings groß sind. Bei solch großen Cluster-States kann das Aktualisieren lange dauern, da alle Aktualisierungen über einen einzelnen Thread abgewickelt werden müssen, um vor der Verteilung der Änderungen im Cluster die Konsistenz zu garantieren.


TIPP: Um die Zahl von Indizes zu reduzieren und große und überbordende Mappings zu vermeiden, sollten Sie darüber nachdenken, Daten nicht nach Herkunftsort aufzuteilen, sondern alle Daten mit ähnlicher Struktur im selben Index zu speichern. Es ist wichtig, einen guten Ausgleich zwischen der Zahl der Indizes und Shards und der Mapping-Größe für jeden einzelnen Index zu finden. Da der Cluster-State auf jedem Node (auch auf den Master-Node) in den Heap geladen wird und sich die Heap-Größe direkt proportional zur Zahl der Indizes, der Felder pro Index und der Shards verhält, sollte auch die Heap-Nutzung im Auge behalten und sichergestellt werden, dass die Größen stimmen.  


Jede Shard verfügt über Daten, die im Hauptspeicher gehalten werden müssen und Platz im Heap beanspruchen. Dazu gehören Datenstrukturen, die Informationen auf Shard-Ebene enthalten, aber auch Strukturen mit Informationen auf Segmentebene, um bestimmen zu können, wo sich die Daten auf dem Speichermedium befinden. Die Größe dieser Datenstrukturen ist variabel und hängt vom jeweiligen Anwendungsfall ab.

Für den segmentbezogenen Overhead ist jedoch festzustellen, dass seine Größe sich nicht streng proportional zur Segmentgröße verhält. Das bedeutet, dass größere Segmente weniger Overhead pro Datenvolumen haben als kleinere. Dieser Unterschied kann durchaus beträchtlich sein.

Um pro Node möglichst viele Daten speichern zu können, wird es wichtig, die Heap-Nutzung zu verwalten und die Overhead-Größe so weit wie möglich klein zu halten. Je mehr Heap-Platz ein Node hat, desto mehr Daten und Shards kann er verarbeiten.

Aus Cluster-Perspektive sind Indizes und Shards daher nicht für umsonst zu haben, da es für jeden Index und jede Shard ein gewisses Maß an Ressourcen-Overhead gibt.


TIPP: Kleine Shards führen zu kleinen Segmenten, was den Overhead vergrößert. Versuchen Sie, die durchschnittliche Shard-Größe in einem Bereich zwischen ein paar GB und ein paar zig GB zu halten. In Anwendungsfällen mit zeitbasierten Daten liegen die Shard-Größen häufig zwischen 20 GB und 40 GB.

TIPP: Da der Overhead pro Shard von der Zahl und Größe der Segmente abhängt, kann eine forcemerge-Operation zum erzwungenen Zusammenführen kleinerer Segmente zu größeren den Overhead eindämmen und die Abfrage-Performance verbessern. Dies sollte idealerweise erst dann geschehen, wenn keine Daten mehr in den Index geschrieben werden. Bedenken Sie, dass es sich dabei um eine teure Operation handelt, die in der Regel außerhalb der Hauptbetriebszeiten durchgeführt werden sollte.

TIPP: Die Zahl der Shards pro Node verhält sich proportional zur verfügbaren Heap-Menge, aber es gibt seitens Elasticsearch keine feste Obergrenze. Als Faustregel lässt sich sagen, dass die Zahl der Shards pro Node pro konfiguriertes Heap-GB unter 20 bleiben sollte. Ein Node mit einem 30-GB-Heap sollte also nicht mehr als 600 Shards haben – je weniger, desto besser. Das trägt dazu bei, dass der Cluster gesund bleibt. (Hinweis: Mit Veröffentlichung von Version 8.3 haben wir die Heap-Nutzung pro Shard drastisch reduziert. Daher ändern wir die Faustregel in diesem Blogpost. Für alle Versionen ab 8.3 gilt daher die im folgenden Tipp genannte Obergrenze.)

NEUER TIPP: Bei Daten-Nodes sollten jedem Feld pro Index 1 kB Heap-Speicher und zusätzlicher Heap-Speicher für die zugehörigen Overheads zur Verfügung stehen
Die exakte Ressourcennutzung der einzelnen zugeordneten Felder hängt vom jeweiligen Feldtyp ab. Als Faustregel lässt sich jedoch sagen, dass pro zugeordnetes Feld pro Index im Daten-Node 1 kB Heap-Speicher verfügbar sein sollte. Darüber hinaus muss es ausreichend Heap-Speicher für die Grundnutzung durch Elasticsearch sowie für Ihre Workloads, also für Aufgaben wie Indexieren, Suchen und Aggregationen, geben. Für viele normale Workloads dürften 0,5 GB reichen – bei sehr leichten Workloads möglicherweise auch weniger und bei schwereren Workloads auch mal mehr.

Wenn ein Daten-Node zum Beispiel Shards aus 1000 Indizes beherbergt, die jeweils 4000 zugeordnete Felder haben, sollten Sie für die Felder ca. 1000 × 4000 × 1 kB = 4 GB Heap-Speicher einplanen sowie für die Workload und andere Overheads noch einmal zusätzliche 0,5 GB. Dieser Node benötigt damit einen Heap-Speicher mit einer Größe von mindestens 4,5 GB.


Wie wirkt sich die Shard-Größe auf die Performance aus?

In Elasticsearch wird jede Abfrage in einem einzigen Thread pro Shard ausgeführt. Es ist aber möglich, mehrere Shards parallel zu verarbeiten und mehrere Abfragen und Aggregationen gegen dieselbe Shard laufen zu lassen.

Das bedeutet, dass die Mindestwartezeit bei Abfragen ohne Caching von den Daten, der Art der Abfrage und der Größe der Shard abhängt. Das Abfragen vieler kleiner Shards verkürzt zwar die Verarbeitungszeit pro Shard, aber da viele weitere Aufgaben in die Warteschlange gestellt und nacheinander abgearbeitet werden müssen, sind die Abfragezeiten nicht zwingend kürzer als beim Abfragen einer kleinen Zahl größerer Shards. Der Umgang mit vielen kleinen Shards kann sich auch negativ auf den Abfragedurchsatz auswirken, wenn es gilt, mehrere Abfragen gleichzeitig zu verarbeiten.


TIPP: Wenn bei der Bestimmung der maximalen Shard-Größe vor allem die Abfrage-Performance im Vordergrund stehen soll, sollte ein Benchmarking mit realistischen Daten und Abfragen durchgeführt werden. Die dabei verwendete Abfrage- und Indexierungslast sollte stets dem entsprechen, was der Node unter Produktionsbedingungen zu verarbeiten hat. Eine Optimierung für eine einzelne Abfrage kann zu verfälschten Ergebnissen führen.


Wie verwalte ich die Shard-Größe?

Bei der Verwendung zeitbasierter Indizes ist jeder Index traditionell mit einem festgelegten Zeitraum verknüpft. Sehr häufig sind Tagesindizes, die gern für das Speichern von Daten mit kurzer Aufbewahrungszeit oder großem Tagesaufkommen verwendet werden. Mit ihnen lässt sich die Aufbewahrungszeit sehr detailliert verwalten und sie erlauben eine einfache Anpassung an sich täglich ändernde Datenaufkommen. Für Daten mit einer längeren Aufbewahrungszeit kommen häufig Wochen- oder Monatsindizes zum Einsatz, vor allem dann, wenn das tägliche Aufkommen für Tagesindizes nicht groß genug ist, um die Shard-Größe aufrechtzuerhalten. Damit müssen im Cluster im Laufe der Zeit weniger Indizes und Shards gespeichert werden.


TIPP: Wenn Sie zeitbasierte Indizes nutzen, die einen festen Zeitpunkt abdecken, passen Sie den von den einzelnen Indizes abgedeckten Zeitraum an die Aufbewahrungszeit und das zu erwartende Datenaufkommen an, um die Ziel-Shard-Größe zu erreichen.


Zeitbasierte Indizes mit einem festen Zeitintervall eignen sich für Anwendungsfälle, in denen das Datenaufkommen relativ vorhersehbar ist und sich nur langsam ändert. Wenn es beim Indexierungstempo schnelle Schwankungen geben kann, ist es sehr schwierig, eine gleichmäßige Ziel-Shard-Größe aufrechtzuerhalten.

Für diese Art von Szenario gibt es die Rollover-Index-API und die Shrink-Index-API. Diese APIs ermöglichen eine deutlich flexiblere Verwaltung von Indizes und Shards, insbesondere wenn es um zeitbasierte Indizes geht.

Mit der Rollover-Index-API können Sie die Zahl der Dokumente angeben, die ein Index enthalten sollte, und/oder festlegen, wie lange Dokumente in diesen Index geschrieben werden sollen. Nachdem eines dieser Kriterien erreicht wurde, kann Elasticsearch das Erstellen eines neuen Index auslösen, sodass es ohne Downtime möglich ist, weiterhin Daten in Indizes zu schreiben. Es ist nicht mehr nötig, dass jeder Index einen bestimmten Zeitraum abdeckt, sondern der Wechsel auf einen neuen Index kann bei Erreichen einer bestimmten Größe ausgelöst werden. Das macht es einfacher, für eine einheitliche Shard-Größe für alle Indizes zu sorgen.

Wenn die Daten eventuell aktualisiert werden, geht bei Nutzung dieser API der Zusammenhang zwischen dem Zeitstempel des Ereignisses und dem Index, in dem sich die Daten befinden, verloren. Das kann sich deutlich negativ auf die Effizienz von Aktualisierungen auswirken, weil jeder Aktualisierung unter Umständen eine Suche vorausgehen muss.


TIPP: Wenn Sie zeitbasierte, unveränderliche Daten haben, deren Aufkommen deutlichen Schwankungen unterliegt, empfiehlt es sich, die Rollover-Index-API zu verwenden, um durch dynamisches Variieren des vom jeweiligen Index abgedeckten Zeitraums eine optimale Ziel-Shard-Größe zu erreichen. Das ermöglicht eine flexible Herangehensweise und kann dazu beitragen, bei unvorhersehbaren Datenaufkommen zu große oder zu kleine Shards zu vermeiden.


Mit der Shrink-Index-API können Sie einen vorhandenen Index in einen neuen Index mit weniger primären Shards umwandeln. Diese API eignet sich für Fälle, in denen die gleichmäßige Verteilung von Shards auf alle Nodes beim Indexieren zu zu kleinen Shards führen würde. Die API reduziert die Zahl der primären Shards, nachdem der Index für das weitere Schreiben von Daten geschlossen wurde, sodass größere Shards entstehen, die für die langfristige Speicherung von Daten besser geeignet sind.


TIPP: Wenn jeder Index einen bestimmten Zeitraum abdecken soll, Sie aber weiterhin in der Lage sein möchten, das Indexieren auf eine große Zahl von Nodes zu verteilen, können Sie mit der Shrink-Index-API die Zahl der primären Shards reduzieren, sobald der Index für das Schreiben von Daten geschlossen wurde. Diese API können Sie auch verwenden, wenn Sie feststellen, dass Sie zu viele Shards konfiguriert haben und die Zahl der Shards reduzieren möchten.


Fazit

In diesem Blogpost haben Sie Tipps und praktische Anleitungen zur Verwaltung von Daten in Elasticsearch erhalten. Wenn Sie mehr erfahren möchten, finden Sie in „Elasticsearch: The Definitive Guide“ einen Abschnitt zum Designen unter Skalierbarkeitsgesichtspunkten, der sich trotz seines leicht betagten Alters immer noch zu lesen lohnt.

Viele der Entscheidungen zur bestmöglichen Verteilung von Daten auf Indizes und Shards sind jedoch kontextabhängig und es ist nicht immer ganz einfach zu bestimmen, wie die verfügbaren Ratschläge am besten umgesetzt werden sollten. Wenn Sie ausführlichere und persönliche Beratung wünschen, können Sie ein Abonnement abschließen und sich von unseren Teams für Support und Consulting bei der schnellen Umsetzung Ihres Projekts helfen lassen. Und wenn Ihr Anwendungsfall auch öffentlich besprochen werden kann, erhalten Sie in unserer Community und unserem öffentlichen Forum ebenfalls Rat und Unterstützung.


Dieser Blogpost erschient zuerst am 18. September 2017. Die englische Fassung wurde am 6. Juli 2022 aktualisiert.