Engineering

Verbessern der Knotenresilienz mit dem „Real Memory Circuit Breaker“

Sie möchten natürlich darauf vertrauen können, dass Elasticsearch auch dann noch Ihren Suche-Traffic zuverlässig verarbeitet, wenn Ihre Website stark unter Last steht. Da Elasticsearch ein verteiltes System ist, ist es von vornherein so aufgebaut, dass es beim Ausfall einzelner Knoten Resilienz bietet. In Elasticsearch 7.0.0 haben wir nun einen neuen und beträchtlich verbesserten Algorithmus zur Cluster-Koordinierung implementiert.

Auch beim Erstellen einzelner Knoten in Elasticsearch wird großer Wert auf Resilienz gelegt. Wenn Sie einem Knoten zu viele Anfragen senden oder die Anfragen zu groß sind, wehrt er sich. Für Letzteres haben wir Schutzschalter eingebaut, die im Pfad der Anfrageverarbeitung an bestimmten Punkten platziert werden, z. B. an dem Punkt, an dem eine Netzwerkanfrage den Knoten erreicht, oder am Punkt direkt vor der Ausführung einer Aggregation. Der Grundgedanke dabei ist, Fehlermeldungen wegen fehlendem Arbeitsspeicher zu vermeiden, indem vorab eingeschätzt wird, ob eine Anfrage den Knoten überlasten würde. Wenn dies der Fall ist, wird die Anfrage gegebenenfalls abgelehnt, statt einen Ausfall zu riskieren. Neben den Schutzschaltern für einzelne Aspekte, wie alle In-Flight-Anfragen oder den Felddaten-Schutzschalter, verfügt Elasticsearch auch über eine Art „General-Schutzschalter“, der alle Schutzschalter im System im Blick behält. Damit kann Elasticsearch Anfragen ablehnen, die auf der Ebene des einzelnen Schutzschalters durchgewinkt würden, aber für das System als Ganzes eine Überforderung darstellen.

Jede einzelne Speicherzuweisung überwachen zu wollen, erweist sich in der Regel als unpraktisch, weshalb Schutzschalter nur Speicher überwachen können, der ausdrücklich reserviert ist. Zudem ist es mitunter nicht möglich, vorab den genauen Speicherbedarf abzuschätzen. Das bedeutet, dass mit Schutzschaltern nie ein ideales Ergebnis erzielt werden kann. Sie bieten zwar ein gewisses Maß an Resilienz gegenüber einer Knotenüberlastung, es kann aber nicht ausgeschlossen werden, dass Knoten doch einmal wegen fehlendem Arbeitsspeicher „aussteigen“. Je kleiner der Heap ist, desto problematischer ist das, da der relative Overhead bei nicht überwachtem Speicher größer ist.

Entwickeln (und Testen) eines besseren Schutzschalters

Was, wenn es möglich wäre herauszufinden, wie viel Speicherplatz ein Knoten bei einer entsprechenden Reservierung im Schutzschalter tatsächlich belegt? Dann könnten wir doch Anfragen anhand des tatsächlichen Systemzustands an diesem Punkt ablehnen, statt uns auf eine Schätzung anhand der aktuellen Reservierungen über alle Schutzschalter hinweg verlassen zu müssen. Genau das haben wir beim neuen „Real Memory Circuit Breaker“ in Elasticsearch 7.0 getan, einem Schutzschalter, der auf der Basis der tatsächlichen Speichernutzung arbeitet. Dabei handelt es sich um eine alternative Implementierung des General-Schutzschalters, bei der eine Funktionalität in der JVM zum Messen der aktuellen Speichernutzung zum Einsatz kommt, statt nur den aktuell überwachten Speicher ins Kalkül zu ziehen. Das ist zwar kostspieliger, als ein paar Zahlen zusammenzuaddieren, aber alles in allem immer noch ziemlich ressourcenschonend: In Mikrobenchmarks haben wir Overheads von 400 bis 900 Nanosekunden gemessen. Zum Testen der Effektivität des „Real Memory Circuit Breakers“ unter unterschiedlichen Bedingungen haben wir eine Reihe von Experimenten durchgeführt. In einem Szenario haben wir eine Volltextindexierungs-Benchmark gegen einen Knoten laufen lassen, der lediglich mit einem 256-MB-Heap bestückt war. Während frühere Versionen von Elasticsearch dieser Last nicht standhalten können und fast auf der Stelle eine OutOfMemoryError-Fehlermeldung zurückgeben, hält der „Real Memory Circuit Breaker“ dagegen und ermöglicht es Elasticsearch, die Last zu verarbeiten. Zu beachten ist, dass Elasticsearch in solchen Fällen eine Fehlerantwort zurückgibt und es Aufgabe der Clients ist, angemessene Backoff- und Neuversuchsmechanismen zu implementieren. Falls Sie schon einen unserer offiziellen Sprach-Clients benutzen, machen wir Ihnen dies natürlich so einfach wie möglich. Die Clients für .NET, Ruby, Python und Java implementieren diese Neuversuchsrichtlinien bereits, und sie bieten zudem Erweiterungen für die Bulk-Indexierung.

In einem anderen Experiment haben wir eine Aggregation ausgeführt, die bei einem Knoten mit einem 16-GB-Heap absichtlich eine unrealistisch hohe Zahl von Buckets produziert hat. Auch hier war wieder zu beobachten, dass frühere Elasticsearch-Versionen aufgrund von fehlendem Speicher schnell kapitulierten, während die Aggregation fast eine halbe Stunde durchgehalten hat, bevor die Fehlermeldung ausgeworfen wurde. Mit dem „Real Memory Circuit Breaker“ hat der Knoten stattdessen eine Antwort zurückgegeben, abhängig davon, ob wir die Ausgabe von Teilergebnissen schon nach etwas mehr als einer Minute oder nach rund zwanzig Minuten erlaubt haben. Aufgrund der Ergebnisse verschiedener Experimente haben wir den Standardwert für den neuen Schutzschalter auf 95 % des insgesamt verfügbaren Heaps festgelegt. Das heißt, dass Elasticsearch den „Real Memory Circuit Breaker“ auslöst, sobald die Heap-Nutzung 95 % erreicht.

Nehmen wir als Beispiel eine Situation, in der eine Bulk-Anfrage gesendet wird, die so klein ist, dass sie alle anderen Prüfungen passiert, die aber den echten Speicher-Schutzschalter auslöst, da sonst die Überlastungslimits des Knotens überschritten würden. Für diesen Knoten ist ein 128-MB-Heap konfiguriert, was heißt, dass angesichts des Standardwerts von 95 % der Auslösewert des General-Schutzschalters bei 117,5 MB liegt. Wenn die Beispielanfrage gesendet wird, antwortet der Knoten mit einem HTTP-429-Fehler mit den folgenden Details:

{
  'error': {
    'type': 'circuit_breaking_exception',
    'reason': '[parent] Data too large, data for [<http_request>] would be [123848638/118.1mb], which is larger than the limit of [123273216/117.5mb], real usage: [120182112/114.6mb], new bytes reserved: [3666526/3.4mb]',
    'bytes_wanted': 123848638,
    'bytes_limit': 123273216,
    'durability': 'TRANSIENT'
  },
  'status': 429
}

Wie wir sehen, gibt der Schutzschalter auch an, dass es sich um einen vorübergehenden Ausfall handelt ('durability': 'TRANSIENT'). Clients können dies als Hinweis darauf nehmen, dass sie es nach einer gewissen Zeit noch einmal versuchen können. Grundlage für die Entscheidung, ob eine Schutzschalterausnahme dauerhaft oder vorübergehend ist, bildet der Gesamtwert des reservierten Speichers über alle Schutzschalter hinweg. Jedem Schutzschaltertyp ist eine Dauer (durability) zugeordnet. Wenn der größte Teil des Speichers durch Schutzschalter mit vorübergehender Dauer (TRANSIENT) reserviert ist, behandelt der echte Speicher-Schutzschalter diese Situation als einen vorübergehenden Zustand. Andernfalls wird die Situation als dauerhaft eingestuft.

Fazit

Wenngleich es in bestimmten Szenarien weiterhin möglich ist, dass ein Elasticsearch-Knoten wegen fehlenden Arbeitsspeichers ausfällt, sorgt der neue „Real Memory Circuit Breaker“ in Elasticsearch für eine deutliche Verbesserung der Resilienz einzelner Knoten. Anfragen werden nicht mehr auf der Grundlage des von Schutzschaltern überwachten Speichers, sondern nach Betrachtung der tatsächlichen Speichernutzung abgelehnt. In unseren Experimenten gelang es Elasticsearch, Lasten zu bewältigen, die für frühere Versionen absolut unerreichbar waren. Mit dem neuen „Real Memory Circuit Breaker“ sind Produktions-Cluster deutlich widerstandsfähiger gegenüber Lastspitzen. Wenn Sie den „Real Memory Circuit Breaker“ ausprobieren möchten, laden Sie die neueste 7.0-Beta-Version herunter, testen Sie sie und lassen Sie uns Ihre Meinung wissen.

Das Bild am Beginn dieses Blogposts wurde von Kiran Raja Bahadur SRK unter der CC BY-NC-ND 2.0-Lizenz bereitgestellt (Originalquelle).