Elasticsearch-Cluster mit 200 TB+

Elasticsearch-Cluster mit 200 TB+

Viele stehen vor der Herausforderung, elasticsearch zu nutzen. Aber was passiert, wenn man damit Logs „in besonders großem Umfang“ speichern möchte? Und das auch noch ohne schmerzhaft den Ausfall eines der mehreren Rechenzentren zu erleben? Wie sollte die Architektur aussehen und auf welche Stolpersteine könnte man stoßen?

Wir bei Odnoklassniki haben uns entschieden, mit elasticsearch das Problem des Log-Managements zu lösen, und teilen nun unsere Erfahrungen mit Habr: sowohl zur Architektur als auch zu den Stolpersteinen.

Ich bin Peter Zaitsev, arbeite als Systemadministrator bei Odnoklassniki. Zuvor war ich auch Administrator und habe mit Manticore Search, Sphinx Search und Elasticsearch gearbeitet. Wenn es noch eine andere …search geben sollte, werde ich wahrscheinlich auch damit arbeiten. Außerdem engagiere ich mich in mehreren Open-Source-Projekten auf freiwilliger Basis.

Als ich zu Odnoklassniki kam, sagte ich leichtfertig im Vorstellungsgespräch, dass ich mit Elasticsearch umgehen kann. Nachdem ich mich eingearbeitet hatte und einige einfache Aufgaben erledigte, bekam ich eine große Aufgabe zur Reformierung des Log-Management-Systems, das zu diesem Zeitpunkt existierte.

Anforderungen

Die Anforderungen an das System waren wie folgt formuliert:

  • Als Frontend sollte Graylog verwendet werden. Denn das Unternehmen hatte bereits Erfahrung mit diesem Produkt, die Programmierer und Tester kannten es, es war für sie vertraut und komfortabel.
  • Datenvolumen: im Durchschnitt 50-80 Tausend Nachrichten pro Sekunde, aber wenn etwas kaputt geht, ist der Datenverkehr unbegrenzt, das können 2-3 Millionen Zeilen pro Sekunde sein.
  • Nachdem wir mit den Kunden die Anforderungen an die Verarbeitungsgeschwindigkeit der Suchanfragen besprochen hatten, verstanden wir, dass das typische Nutzungsmuster eines solchen Systems ist, dass die Leute die Logs ihrer Anwendung der letzten zwei Tage suchen und nicht länger als eine Sekunde auf das Ergebnis ihrer Anfrage warten möchten.
  • Die Administratoren bestanden darauf, dass das System bei Bedarf einfach skalierbar ist, ohne dass sie tief in die Funktionsweise eintauchen müssen.
  • Die einzige Wartungsaufgabe, die diese Systeme periodisch benötigten, bestand darin, etwas Hardware auszutauschen.
  • Außerdem gibt es bei Odnoklassniki eine hervorragende technische Tradition: Jeder Service, den wir starten, muss einen Ausfall des Rechenzentrums (plötzlich, ungeplant und jederzeit) überstehen.

Die letzte Anforderung für die Umsetzung dieses Projekts hat uns am meisten gefordert, worüber ich später näher berichten werde.

Umgebung

Wir arbeiten in vier Rechenzentren, wobei die Elasticsearch-Datenknoten nur in dreien untergebracht werden können (aus verschiedenen nicht-technischen Gründen).

In diesen vier Rechenzentren befinden sich etwa 18.000 verschiedene Log-Quellen – Hardware, Container, virtuelle Maschinen.

Eine wichtige Besonderheit: Der Cluster wird in Containern gestartet Podman nicht auf physischen Maschinen, sondern auf unserem eigenen Cloud-Produkt one-cloud. Den Containern werden 2 Kerne garantiert, ähnlich wie 2.0Ghz v4, mit der Möglichkeit, die anderen Kerne bei Leerlauf zu nutzen.

Mit anderen Worten:

Elasticsearch-Cluster mit 200 TB+

Topologie

Das Gesamtbild der Lösung erschien mir ursprünglich wie folgt:

  • 3-4 VIPs stehen hinter dem A-Record der Domain Graylog, das ist die Adresse, an die die Logs gesendet werden.
  • Jeder VIP fungiert als LVS-Lastenausgleich.
  • Danach gelangen die Logs in eine Graylog-Batterie, einige Daten werden im GELF-Format und einige im Syslog-Format gesendet.
  • Anschließend werden all dies in großen Batches in eine Batterie von Elasticsearch-Koordinatoren geschrieben.
  • Diese senden dann Lese- und Schreibanfragen an die entsprechenden Datenknoten.

Elasticsearch-Cluster mit 200 TB+

Terminologie

Möglicherweise sind nicht alle mit der Terminologie vertraut, daher möchte ich etwas darauf eingehen.

In Elasticsearch gibt es mehrere Knotentypen – Master, Koordinator, Datenknoten. Es gibt noch zwei weitere Typen für verschiedene Log-Transformationen und zur Verbindung verschiedener Cluster, aber wir haben nur die genannten verwendet.

Master
Pingt alle im Cluster vorhandenen Knoten an, hält die aktuelle Clusterkarte aufrecht und verbreitet sie zwischen den Knoten, verarbeitet die Ereignislogik und kümmert sich um verschiedene Arten von clusterweiten Wartungsarbeiten.

Koordinator
Führt eine einzige Aufgabe aus: nimmt Anfragen von Clients zum Lesen oder Schreiben entgegen und leitet diesen Traffic weiter. Wenn die Anfrage für eine Schreiboperation ist, fragt er wahrscheinlich den Master, in welchen Shard des relevanten Indexes er dies ablegen soll, und leitet die Anfrage weiter.

Datenknoten
Speichert Daten, führt ankommende Suchanfragen und Operationen auf den darauf befindlichen Shards aus.

Graylog
Das ist eine Art Mischung aus Kibana mit Logstash im ELK-Stack. Graylog kombiniert sowohl UI als auch einen Verarbeitungs-Workflow für Logs. Unter der Haube arbeiten Kafka und Zookeeper in Graylog, welche die Konnektivität von Graylog als Cluster gewährleisten. Graylog kann Logs (Kafka) zwischenspeichern, falls Elasticsearch nicht verfügbar ist, und fehlgeschlagene Lese- und Schreibanfragen wiederholen, sowie Logs anhand festgelegter Regeln gruppieren und taggen. Wie Logstash hat Graylog die Funktionalität, Strings vor dem Speichern in Elasticsearch zu modifizieren.

Darüber hinaus verfügt Graylog über eine integrierte Service Discovery, die es ermöglicht, anhand eines verfügbaren Elasticsearch-Knotens eine komplette Cluster-Karte zu erhalten und diese nach einem bestimmten Tag zu filtern, was die gezielte Abwicklung von Anfragen an bestimmte Container ermöglicht.

Visuell sieht das etwa so aus:

Elasticsearch-Cluster mit 200 TB+

Dies ist ein Screenshot von einem bestimmten Instance. Hier bauen wir ein Histogramm basierend auf der Suchanfrage auf und zeigen relevante Zeilen an.

Indizes

Zurück zur Architektur des Systems möchte ich näher darauf eingehen, wie wir das Indexmodell erstellt haben, sodass alles korrekt funktioniert.

In dem vorher gezeigten Diagramm ist dies die unterste Ebene: Elasticsearch-Datenknoten.

Ein Index ist eine große virtuelle Entität, die aus Elasticsearch-Shards besteht. Jeder dieser Shards ist nichts anderes als ein Lucene-Index. Und jeder Lucene-Index besteht wiederum aus einem oder mehreren Segmenten.

Elasticsearch-Cluster mit 200 TB+

Bei der Planung haben wir überlegt, dass wir zur Erfüllung der Anforderungen an die Lesegeschwindigkeit bei großen Datenmengen diese Daten gleichmäßig über die Datenknoten "verteilen" müssen.

Das führte dazu, dass die Anzahl der Shards pro Index (mit Replikaten) streng gleich der Anzahl der Datenknoten sein muss. Erstens, um einen Replikationsfaktor von zwei zu gewährleisten (das heißt, wir können die Hälfte des Clusters verlieren). Und zweitens, um Lese- und Schreibanfragen mindestens auf der Hälfte des Clusters verarbeiten zu können.

Die Speicherzeit haben wir zunächst auf 30 Tage festgelegt.

Die Verteilung der Shards kann grafisch wie folgt dargestellt werden:

Elasticsearch-Cluster mit 200 TB+

Das gesamte dunkelgraue Rechteck ist der Index. Das linke rote Quadrat darin ist der Primary-Shard, der erste im Index. Das blaue Quadrat ist der Replica-Shard. Sie befinden sich in verschiedenen Rechenzentren.

Wenn wir einen weiteren Shard hinzufügen, gelangt er in das dritte Rechenzentrum. Und letztendlich erhalten wir eine Struktur, die den Verlust eines Rechenzentrums ohne Verlust der Datenkonsistenz ermöglicht:

Elasticsearch-Cluster mit 200 TB+

Die Rotation der Indizes, d.h. die Erstellung eines neuen Index und die Löschung des ältesten, haben wir auf 48 Stunden festgelegt (basierend auf dem Nutzungsverhalten des Index: An den letzten 48 Stunden wird am häufigsten gesucht).

Dieser Rotationsintervall der Indizes hängt von folgenden Gründen ab:

Wenn auf eine bestimmte Datenknoten eine Suchanfrage eintrifft, ist es aus Performance-Sicht vorteilhafter, wenn ein Shard befragt wird, dessen Größe mit der Größe des Heap der Knotens vergleichbar ist. Dies ermöglicht es, den "heißen" Teil des Index im Heap zu halten und schnell darauf zuzugreifen. Wenn es viele "heiße Teile" gibt, verschlechtert sich die Suchgeschwindigkeit im Index.

Wenn ein Knoten mit der Ausführung einer Suchanfrage auf einem Shard beginnt, weist er eine Anzahl von Threads zu, die der Anzahl der Hyper-Threading-Kerne der physischen Maschine entspricht. Wenn die Suchanfrage eine große Anzahl von Shards betrifft, wächst die Anzahl der Threads proportional. Dies hat negative Auswirkungen auf die Suchgeschwindigkeit und wirkt sich nachteilig auf die Indizierung neuer Daten aus.

Um die erforderliche Suchlatenz sicherzustellen, haben wir uns entschieden, SSDs zu verwenden. Um Anfragen schnell zu verarbeiten, mussten die Maschinen, auf denen diese Container betrieben wurden, mindestens 56 Kerne haben. Die Zahl 56 wurde als bedingt ausreichend festgelegt und bestimmt die Anzahl der Threads, die Elasticsearch während des Betriebs erzeugt. In Elasticsearch hängen viele Parameter des Thread-Pools direkt von der Anzahl der verfügbaren Kerne ab, was wiederum direkt die erforderliche Anzahl von Knoten im Cluster nach dem Prinzip "weniger Kerne — mehr Knoten" beeinflusst.

Letztendlich haben wir festgestellt, dass ein Shard im Durchschnitt etwa 20 Gigabyte wiegt, und auf 1 Index entfallen 360 Shards. Wenn wir diese alle alle 48 Stunden rotieren, haben wir 15 Stück. Jeder Index enthält Daten für 2 Tage.

Datenaufzeichnungs- und -leseschemata

Lass uns klären, wie in diesem System Daten aufgezeichnet werden.

Angenommen, wir erhalten von Graylog eine Anfrage im Koordinator. Zum Beispiel möchten wir 2-3 Tausend Zeilen indizieren.

Der Koordinator erhält von Graylog eine Anfrage und fragt den Master: „In der Indizierungsanfrage war der Index konkret angegeben, aber es wurde nicht angegeben, in welchen Shard dies geschrieben werden soll.“

Der Master antwortet: „Schreibe diese Informationen in Shard Nummer 71“, danach wird sie direkt an den relevanten Datenknoten gesendet, wo sich der primäre Shard Nummer 71 befindet.

Danach wird das Transaktionsprotokoll auf den Replica-Shard repliziert, der sich bereits in einem anderen Rechenzentrum befindet.

Elasticsearch-Cluster mit 200 TB+

Von Graylog kommt eine Suchanfrage an den Koordinator. Der Koordinator leitet sie nach Index weiter, wobei Elasticsearch die Anfragen nach dem Round-Robin-Prinzip zwischen dem primären Shard und dem Replica-Shard verteilt.

Elasticsearch-Cluster mit 200 TB+

Die Knoten, insgesamt 180, reagieren ungleichmäßig, und während sie antworten, sammelt der Koordinator die Informationen, die bereits von schnelleren Datenknoten „ausgespuckt“ wurden. Danach, wenn entweder alle Informationen eingetroffen sind oder der Zeitüberschreitungswert für die Anfrage erreicht wurde, gibt er alles direkt an den Kunden zurück.

Das gesamte System verarbeitet im Durchschnitt Suchanfragen der letzten 48 Stunden in 300-400 ms, mit Ausnahme der Anfragen, die mit leading wildcard versehen sind.

„Kleinigkeiten“ mit Elasticsearch: Java-Einstellungen

Elasticsearch-Cluster mit 200 TB+

Damit all dies so funktioniert, wie wir es uns ursprünglich vorgestellt hatten, haben wir sehr lange an den unterschiedlichsten Aspekten im Cluster gefeilt.

Der erste Teil der entdeckten Probleme stand im Zusammenhang damit, wie Java in Elasticsearch standardmäßig voreingestellt ist.

Problem eins
Wir haben eine sehr große Anzahl von Meldungen beobachtet, dass auf Lucene-Ebene, wenn Hintergrundaufgaben laufen, die Merges der Lucene-Segmente mit Fehlern enden. In den Protokollen war zu sehen, dass dies ein OutOfMemoryError war. Anhand der Telemetriedaten sahen wir, dass der Heap frei war, und es war unklar, warum diese Operation fehlschlug.

Es stellte sich heraus, dass die Merges der Lucene-Indizes außerhalb des Heaps stattfinden. Die Container sind bei den verbrauchten Ressourcen ziemlich streng begrenzt. In diese Ressourcen passte nur der Heap (der Wert heap.size war ungefähr gleich dem RAM), während bestimmte Off-Heap-Operationen bei einer fehlenden Einhaltung der ~500 MB, die bis zur Grenze verbleiben, mit einer Speicherzuweisungsfehler abstürzten.

Die Lösung war ziemlich trivial: Das für den Container verfügbare RAM-Volumen wurde erhöht, danach hatten wir keine Probleme mehr.

Problem zwei
Nach etwa 4-5 Tagen nach dem Start des Clusters bemerkten wir, dass die Datenknoten gelegentlich aus dem Cluster fielen und nach 10-20 Sekunden wieder eintraten.

Als wir anfingen, das Problem zu untersuchen, stellte sich heraus, dass der off-heap Speicher in Elasticsearch praktisch nicht kontrolliert wird. Als wir dem Container mehr Speicher zuwiesen, erhielten wir die Möglichkeit, verschiedene Informationen in den Direct Buffer Pools zu speichern, und dieser wurde nur nach einem expliziten GC von Elasticsearch geleert.

In einigen Fällen dauerte dieser Vorgang ziemlich lange, und in der Zwischenzeit hatte der Cluster diese Node bereits als ausgefallen markiert. Dieses Problem ist gut dokumentiert hier.

Die Lösung war folgende: Wir beschränkten die Java-Möglichkeit, den Großteil des Speichers außerhalb des Heaps für diese Operationen zu nutzen. Wir limitierten ihn auf 16 Gigabyte (-XX:MaxDirectMemorySize=16g), wodurch der explizite GC deutlich häufiger aufgerufen wurde und viel schneller arbeitete, wodurch wir den Cluster nicht weiter destabilisierten.

Das dritte Problem
Wenn Sie denken, dass die Probleme mit den "Nodes, die den Cluster im ungünstigsten Moment verlassen" damit beendet sind, liegen Sie falsch.

Als wir die Arbeit mit Indizes konfigurierten, entschieden wir uns für mmapfs, um die Suchzeit zu verkürzen bei neuen Shards mit hoher Segmentierung. Das war ein ziemlich grober Fehler, weil beim Einsatz von mmapfs die Datei in den Arbeitsspeicher gemappt wird, und wir danach mit der gemappten Datei arbeiten. Dadurch passiert es, dass wir beim Versuch, den GC zu stoppen, sehr lange zum Safepoint gelangen, und auf dem Weg dorthin hört die Anwendung auf, auf Anfragen des Masters zu antworten, ob sie noch lebt. Folglich denkt der Master, dass die Node nicht mehr im Cluster ist. Nachdem der Garbage Collector nach etwa 5-10 Sekunden arbeitet, belebt sich die Node wieder, kehrt in den Cluster zurück und beginnt mit der Initialisierung der Shards. Das alles erinnerte stark an das “Produkt, das wir verdient haben” und war für irgendetwas Ernsthaftes nicht geeignet.

Um ein solches Verhalten zu vermeiden, haben wir zunächst auf das Standard niofs gewechselt und später, nachdem wir von der fünften auf die sechste Version von Elastic migriert hatten, hybridfs ausprobiert, wo dieses Problem nicht auftauchte. Mehr über die Arten von Speicher kann man lesen hier.

Das vierte Problem
Dann gab es noch ein sehr aufschlussreiches Problem, das wir rekordverdächtig lange behandelt haben. Wir haben es 2-3 Monate lang verfolgt, da das Muster absolut unklar war.

Manchmal gingen unsere Koordinatoren in den Full GC, normalerweise nachmittags, und kehrten von dort nicht zurück. Dabei sah es beim Protokollieren der GC-Verzögerungen so aus: alles lief gut, gut, gut, und dann plötzlich – alles war plötzlich schlecht.

Zuerst dachten wir, dass wir es mit einem bösartigen Nutzer zu tun hatten, der eine Anfrage startet, die den Koordinator aus dem Arbeitsmodus wirft. Wir haben sehr lange Anfragen protokolliert, um herauszufinden, was passiert.

Letztendlich stellte sich heraus, dass in dem Moment, wenn ein Benutzer eine sehr große Anfrage stellt und diese auf einen bestimmten Elasticsearch-Koordinator trifft, einige Knoten länger antworten als andere.

Und während der Koordinator auf die Antworten aller Knoten wartet, sammelt er die Ergebnisse von bereits antwortenden Knoten. Für den GC bedeutet das, dass sich unser Heap-Nutzungsmuster sehr schnell ändert. Und der GC, den wir verwendet haben, konnte mit dieser Aufgabe nicht umgehen.

Die einzige Lösung, die wir gefunden haben, um das Verhalten des Clusters in solch einer Situation zu ändern, war die Migration auf JDK13 und die Verwendung des Garbage Collectors Shenandoah. Das hat das Problem gelöst, unsere Koordinatoren fielen nicht mehr aus.

Damit endeten die Probleme mit Java und die Probleme mit der Durchsatzleistung begannen.

«Bärchen» mit Elasticsearch: Durchsatzleistung

Elasticsearch-Cluster mit 200 TB+

Durchsatzprobleme bedeuten, dass unser Cluster stabil arbeitet, aber in Spitzenzeiten der zu indexierenden Dokumente und während der Manöver die Leistung nicht ausreicht.

Das erste häufige Symptom: Bei „Explosionen“ im Produktionsbetrieb, wenn plötzlich eine sehr große Menge an Protokollen generiert wird, tritt in Graylog häufig der Indexierungsfehler es_rejected_execution auf.

Das geschah, weil thread_pool.write.queue auf einem Dataknoten standardmäßig nur 200 Anfragen cachen kann, bevor Elasticsearch die Anfrage zur Indexierung verarbeitet und die Informationen auf das Shard schreibt. Und in der Elasticsearch-Dokumentation wird sehr wenig über diesen Parameter gesagt. Es wird nur die maximale Anzahl der Threads und die Standardgröße angegeben.

Natürlich haben wir diesen Wert angepasst und Folgendes herausgefunden: In unserem Setup kann bis zu 300 Anfragen recht gut zwischengespeichert werden, während ein höherer Wert bedenklich ist, da wir sonst wieder in den Full GC geraten.

Außerdem musste Graylog so konfiguriert werden, dass es nicht häufig und in kleinen Stapeln, sondern in großen Stapeln oder alle 3 Sekunden, wenn der Stapel noch nicht voll ist, schreibt, da es sich um Pakete von Nachrichten handelt, die im Rahmen einer Anfrage ankommen. In diesem Fall wird die Information, die wir in Elasticsearch schreiben, nicht in zwei Sekunden, sondern in fünf Sekunden zugänglich, was für uns akzeptabel ist, aber die Anzahl der Retries verringert, die nötig sind, um einen großen Informationsstapel durchzuschieben.

Dies ist besonders wichtig in Momenten, in denen etwas irgendwo abgestürzt ist und darüber heftig berichtet wird, um nicht einen vollständig zugespammten Elasticsearch zu erhalten und nach einer Weile nicht mehr funktionierende Graylog-Knoten aufgrund überfüllter Puffer.

Außerdem gab es Beschwerden von Programmierern und Testern, wenn diese Explosionen in der Produktion auftraten: In dem Moment, in dem sie diese Protokolle dringend benötigten, wurden sie sehr langsam bereitgestellt.

Wir haben uns darum gekümmert. Auf der einen Seite war klar, dass sowohl die Suchanfragen als auch die Anfragen zur Indizierung im Wesentlichen auf denselben physischen Maschinen verarbeitet werden, was unweigerlich zu bestimmten Einbrüchen führen wird.

Das konnte jedoch teilweise umgangen werden, da in den sechsten Versionen von Elasticsearch ein Algorithmus eingeführt wurde, der es ermöglicht, Anfragen nicht nach dem Zufallsprinzip round-robin (Container, der mit der Indizierung beschäftigt ist und den Primary-Shard hält, kann sehr beschäftigt sein, daher wird es nicht möglich sein, schnell zu antworten), zu verteilen, sondern diese Anfrage an einen weniger belasteten Container mit Replica-Shard zu richten, der deutlich schneller antwortet. Mit anderen Worten, wir sind zu use_adaptive_replica_selection: true gekommen.

Das Bild des Lesens beginnt so auszusehen:

Elasticsearch-Cluster mit 200 TB+

Der Übergang zu diesem Algorithmus hat es möglich gemacht, die Abfragezeiten in Momenten deutlich zu verbessern, in denen ein großer Strom von Protokollen zur Speicherung läuft.

Schließlich bestand das Hauptproblem darin, das Rechenzentrum schmerzfrei auszuleiten.

Was wir vom Cluster sofort nach dem Verlust der Verbindung zu einem Rechenzentrum wollten:

  • Wenn sich im ausgefallenen Rechenzentrum der aktuelle Master befindet, wird er neu gewählt und wechselt als Rolle zu einem anderen Knoten in einem anderen Rechenzentrum.
  • Der Master wird schnell alle nicht erreichbaren Knoten aus dem Cluster werfen.
  • Anhand der verbleibenden Ressourcen wird er verstehen: In dem verlorenen Rechenzentrum hatten wir solche primary-Shards, schnell werden die komplementären replica-Shards in den verbleibenden Rechenzentren gefördert, und wir werden mit der Indizierung der Daten fortfahren.
  • Infolgedessen wird die Durchsatzkapazität des Clusters beim Schreiben und Lesen langsam abnehmen, aber insgesamt wird alles, wenn auch langsam, stabil funktionieren.

Wie sich herausstellte, wollten wir etwas in dieser Art:

Elasticsearch-Cluster mit 200 TB+

Und haben Folgendes erhalten:

Elasticsearch-Cluster mit 200 TB+

Wie kam es dazu?

Im Moment des Ausfalls des Rechenzentrums wurde unser Flaschenhals zum Master.

Warum?

Der Grund dafür ist, dass im Master ein TaskBatcher existiert, der für die Verteilung bestimmter Aufgaben und Ereignisse im Cluster verantwortlich ist. Jeder Ausfall einer Node, jede Beförderung eines Shards von replica zu primary, jede Aufgabe zur Erstellung eines Shards irgendwo — all das gelangt zuerst in den TaskBatcher, wo es sequenziell und in einem Stream verarbeitet wird.

Im Moment des Ausfalls eines Rechenzentrums stellten sich alle Daten-Nodes in den überlebenden Rechenzentren die Aufgabe, dem Master zu melden: "Wir haben solche und solche Shards und Nodes verloren."

Die überlebenden Daten-Nodes schickten all diese Informationen an den aktuellen Master und versuchten zu warten, bis er bestätigte, dass er sie erhalten hatte. Dies warteten sie jedoch vergeblich, da der Master die Aufgaben schneller erhielt, als er antworten konnte. Die Nodes wiederholten die Anfragen aufgrund des Timeouts, und währenddessen versuchte der Master nicht einmal, darauf zu antworten, sondern war vollständig damit beschäftigt, die Anfragen nach Priorität zu sortieren.

In extremer Form bedeutet das, dass die Daten-Nodes den Master derart mit Anfragen überhäuften, dass er in die Full GC ging. Danach wechselte die Rolle des Masters zu einer anderen Node, mit der das Gleiche geschah, und letztendlich brach das Cluster vollständig zusammen.

Wir haben Messungen durchgeführt, und bis zur Version 6.4.0, in der dieses Problem behoben wurde, reichte es aus, nur 10 von 360 Daten-Nodes gleichzeitig abzuschalten, um das Cluster vollständig zum Absturz zu bringen.

So sah das ungefähr aus:

Elasticsearch-Cluster mit 200 TB+

Nach der Version 6.4.0, in der dieser gefährliche Fehler behoben wurde, hörten die Daten-Nodes auf, den Master abzuschalten. Aber "klüger" wurde er dadurch nicht. Denn wenn wir 2, 3 oder 10 (jede Anzahl, die nicht eins ist) Daten-Nodes abschalten, erhält der Master eine erste Nachricht, die besagt, dass Node A ausgefallen ist, und versucht, dies Node B, Node C, Node D zu melden.

Und gegenwärtig kann man nur durch das Festlegen eines Zeitlimits für die Versuche, jemandem von etwas zu erzählen, das etwa 20-30 Sekunden beträgt, dagegen ankämpfen und so die Geschwindigkeit der Ausschaltung des Rechenzentrums aus dem Cluster steuern.

Im Prinzip entspricht dies den Anforderungen, die ursprünglich an das Endprodukt im Rahmen des Projekts gestellt wurden, aber aus der Sicht der 'reinen Wissenschaft' handelt es sich um einen Fehler. Dieser wurde übrigens von den Entwicklern in Version 7.2 erfolgreich behoben.

Wenn eine Datenknoten ausfiel, stellte sich heraus, dass es wichtiger war, die Informationen über ihren Ausfall zu verbreiten, als dem gesamten Cluster mitzuteilen, dass sich darauf bestimmte primary-shards befanden (um replica-shards in einem anderen Rechenzentrum zu einem primary zu befördern, auf dem man Informationen schreiben konnte).

Daher werden die ausgefallenen Datenknoten, wenn alles 'vorüber' ist, nicht sofort als stale markiert. Folglich müssen wir warten, bis alle Pings zu den ausgefallenen Datenknoten abgelaufen sind, und erst dann beginnt unser Cluster, darüber zu informieren, dass dort und dort und dort die Aufzeichnung von Informationen fortgesetzt werden muss. Weitere Einzelheiten können Sie hier nachlesen. hier.

Insgesamt dauert der Vorgang der Ausschaltung des Rechenzentrums heute etwa 5 Minuten zu Stoßzeiten. Für so ein großes und unhandliches Gebilde ist das ein ziemlich gutes Ergebnis.

Schließlich sind wir zu folgender Entscheidung gekommen:

  • Wir haben 360 Datenknoten mit Festplatten zu 700 Gigabyte.
  • 60 Koordinatoren für das Routing des Verkehrs zu diesen Datenknoten.
  • 40 Master, die als Erbe aus den Versionen vor 6.4.0 übrig geblieben sind – um die Ausschaltung des Rechenzentrums zu überstehen, waren wir mental darauf vorbereitet, einige Maschinen zu verlieren, um auch im schlimmsten Szenario einen Master-Quorum zu gewährleisten.
  • Alle Versuche, Rollen in einem Container zu kombinieren, scheiterten daran, dass irgendwann die Knoten unter der Last zusammenbrachen.
  • Im gesamten Cluster wird eine heap.size von 31 Gigabyte verwendet: Alle Versuche, die Größe zu reduzieren, führten dazu, dass bei schweren Suchanfragen mit einem führenden Wildcard entweder einige Knoten abgestürzt sind oder der Circuit Breaker in Elasticsearch selbst ausgelöst wurde.
  • Darüber hinaus haben wir versucht, die Anzahl der Objekte im Cluster so gering wie möglich zu halten, um in der engsten Stelle, die wir bei den Meistern hatten, so wenig Ereignisse wie möglich zu verarbeiten.

Abschließend zum Monitoring

Damit all dies so funktioniert, wie es gedacht war, überwachen wir Folgendes:

  • Jeder Datenknoten berichtet in unser Cloud, dass er vorhanden ist und welche Shards sich darauf befinden. Wenn wir irgendwo etwas abschalten, meldet der Cluster nach 2-3 Sekunden, dass wir in Zentrum A die Knoten 2, 3 und 4 abgeschaltet haben – das bedeutet, dass wir in anderen Rechenzentren auf keinen Fall die Knoten abschalten dürfen, auf denen Shards nur einmal vorhanden sind.
  • Angesichts des Verhaltens des Masters achten wir sehr genau auf die Anzahl ausstehender Aufgaben. Denn selbst eine hängende Aufgabe, wenn sie nicht rechtzeitig abläuft, könnte theoretisch in einer Notsituation der Grund sein, weshalb beispielsweise ein Replica-Shard nicht im Primary verarbeitet werden kann, was die Indizierung zum Stillstand bringen würde.
  • Wir achten auch sehr genau auf die Verzögerungen des Garbage Collectors, da wir damit bereits große Schwierigkeiten bei der Optimierung hatten.
  • Rejects nach Threads, um im Voraus zu verstehen, wo sich der Engpass befindet.
  • Und die Standardmetriken wie Heap, RAM und I/O.

Bei der Überwachung muss man die Eigenheiten des Thread-Pools in Elasticsearch auf jeden Fall berücksichtigen. Die Elasticsearch-Dokumentation beschreibt die Anpassungsmöglichkeiten und Standardwerte für die Suche, die Indizierung, aber schweigt vollständig über thread_pool.management. Diese Threads verarbeiten unter anderem Anfragen wie _cat/shards und ähnliche, die sich gut für die Überwachung verwenden lassen. Je größer der Cluster, desto mehr solcher Anfragen werden pro Zeiteinheit ausgeführt, und der genannte thread_pool.management wird nicht nur nicht in der offiziellen Dokumentation dargestellt, sondern ist auch standardmäßig auf 5 Threads limitiert, was sehr schnell ausgelastet ist, woraufhin die Überwachung nicht mehr korrekt funktioniert.

Was ich abschließend sagen möchte: Wir haben es geschafft! Wir konnten unseren Programmierern und Entwicklern ein Werkzeug geben, das in nahezu jeder Situation schnell und zuverlässig Informationen über das Geschehen in der Produktion bereitstellen kann.

Ja, es war ziemlich komplex, aber dennoch gelang es uns, unsere Wünsche in bereits bestehende Produkte zu integrieren, ohne diese patchen oder umschreiben zu müssen.

Elasticsearch-Cluster mit 200 TB+

Quelle: habr.com

60GB SSD 8Gb DDR4