Softwarearchitektur verstehen und entscheiden

Aaron KreisÜber mich
Ausfallsicherheit

Backpressure

Der Empfänger bremst den Sender

Die Idee und ihr Nutzen

Ein Importdienst einer Versicherung liest nachts zwei Millionen Schadenfälle aus einer Importdatei und gibt sie an einen Bewertungsdienst weiter, der jeden Fall prüft. Der Importdienst kann die Importdatei viel schneller lesen, als der Bewertungsdienst prüfen kann. Ohne Bremse sammeln sich die gelesenen, aber noch nicht geprüften Fälle im Arbeitsspeicher, bis der Arbeitsspeicher voll ist und ein Programm abstürzt. Der langsamere Bewertungsdienst meldet deshalb, wie viele Fälle er als Nächstes annehmen kann, etwa „schicke mir die nächsten 100“, und der Importdienst liest erst dann weiter. So läuft der Import durch, ohne dass ein Speicher überläuft. Dieses Verfahren heißt Backpressure, auf Deutsch Gegendruck. Der Gegendruck ist die Meldung des Bewertungsdienstes, die den Importdienst bremst.

Bausteine: Importdatei: zwei Millionen Schadenfälle; Importdienst: liest abschnittsweise; Bewertungsdienst: prüft etwa 500 Fälle pro Sekunde. Verbindungen: Importdatei → Importdienst; Importdienst → Bewertungsdienst (Fälle); Bewertungsdienst → Importdienst (fordert die nächsten Fälle an). Der Bewertungsdienst gibt vor, wie viele Fälle er annehmen kann; der Importdienst liest erst dann weiter.

Wie funktioniert es?

Um 1 Uhr nachts startet der Importdienst mit der Importdatei von zwei Millionen Schadenfällen. Der Importdienst liest die Importdatei abschnittsweise, also jeweils nur eine begrenzte Zahl von Fällen. Er beginnt mit den ersten 1.000 Fällen. Diese 1.000 Fälle legt der Importdienst in den Puffer, einen Zwischenspeicher, aus dem der Bewertungsdienst die Fälle abholt. Der Puffer fasst höchstens 1.000 Fälle und ist damit voll, und der Importdienst wartet, statt weiterzulesen. Das Team hat die Größe so gewählt, dass der Puffer wenig Speicher belegt und trotzdem für zwei Sekunden Arbeit des Bewertungsdienstes reicht. Der Bewertungsdienst entnimmt Fälle aus dem Puffer und prüft etwa 500 Fälle pro Sekunde, weil er für jeden Fall Vertragsdaten nachschlagen und Regeln anwenden muss. Hat der Bewertungsdienst 100 Fälle geprüft, fordert er beim Importdienst mit der Meldung „schicke mir die nächsten 100“ neue Fälle an. Erst auf diese Anforderung hin liest der Importdienst genau 100 weitere Fälle und legt sie in den Puffer. Der Bewertungsdienst fordert jeweils 100 Fälle an, damit nicht für jeden einzelnen Fall eine Anforderung über das Netz geht. Bis die nächsten 100 Fälle im Puffer liegen, hat der Bewertungsdienst noch 900 Fälle vor sich und läuft deshalb nicht leer. So geht es weiter, bis die Importdatei gelesen ist, und der Puffer enthält nie mehr als 1.000 Fälle, egal wie schnell der Importdienst lesen könnte. Bei gleichmäßig 500 Fällen pro Sekunde braucht der Bewertungsdienst für zwei Millionen Fälle 4.000 Sekunden, also etwas mehr als eine Stunde. Der Import ist deshalb frühestens gegen 2:07 Uhr fertig. Wird der Bewertungsdienst zwischendurch langsamer oder fällt er kurz aus, kommt seltener oder gar keine Anforderung, und der Importdienst legt nichts in den vollen Puffer nach. Kein Programm läuft mit vollem Speicher; der Import dauert nur länger.

Bei Backpressure bestimmt der Empfänger das Tempo, nicht der Sender. Der Bewertungsdienst ist hier der Empfänger, der Importdienst der Sender. Importdienst, Puffer und Bewertungsdienst bilden eine Kette von Stufen, die die Schadenfälle nacheinander weiterreichen. Besteht die Verarbeitung aus mehreren Stufen nacheinander, darf jede Stufe bei der vorherigen nur so viele Daten anfordern, wie die nächste Stufe bei ihr angefordert hat, bis zurück zur Quelle. An der Quelle muss entschieden werden, was mit Daten geschieht, die gerade nicht angenommen werden können. Die Daten können warten wie die Fälle in der Importdatei. Sie können auch zwischengespeichert oder verworfen werden, oder die Quelle nimmt neue Daten gar nicht erst an. Neben der Anforderung durch den Empfänger gibt es zwei weitere verbreitete Formen von Backpressure. Bei der ersten meldet der Empfänger nur „zu viel“, und der Sender verlangsamt sich. Bei der zweiten verwirft der Sender nach einer festen Regel Daten, wenn er nicht warten kann. Ein Sender, der die Messwerte eines Sensors weitergibt, verwirft dann zum Beispiel die ältesten Messwerte.

Nach einem Absturz muss der Importdienst beim richtigen Fall weitermachen. Backpressure hält aber nicht selbst fest, bis zu welchem Fall die Importdatei bereits verarbeitet ist. Dafür braucht der Importdienst eine eigene Aufzeichnung.

Backpressure: der Import liest nur so schnell, wie bewertet wird

Beteiligte: Importdatei: zwei Millionen Schadenfälle; Importdienst: liest abschnittsweise, wartet bei vollem Puffer; Puffer: fasst höchstens 1.000 Fälle; Bewertungsdienst: prüft etwa 500 Fälle pro Sekunde, fordert Nachschub an. Ablauf: 1. Der Importdienst liest um 1 Uhr nicht die ganze Importdatei mit zwei Millionen Schadenfällen, sondern die ersten 1.000 Fälle. 2. Der Importdienst legt die 1.000 Fälle in den Puffer. Der Puffer ist damit voll, und der Importdienst wartet, statt weiterzulesen. 3. Der Bewertungsdienst entnimmt Fälle und prüft sie. 4. Hat der Bewertungsdienst 100 Fälle geprüft, meldet er „schicke mir die nächsten 100“. Das ist die Anforderung an den Importdienst. 5. Der Importdienst liest genau 100 weitere Fälle und legt sie in den Puffer. 6. Der Importdienst füllt nur nach, was der Bewertungsdienst anfordert, und der Puffer enthält nie mehr als 1.000 Fälle. So geht es weiter, bis die Importdatei gelesen ist. 7. Wird der Bewertungsdienst langsamer oder fällt kurz aus, kommt seltener oder gar keine Anforderung, und der Importdienst legt nichts in den vollen Puffer nach. Kein Programm läuft mit vollem Speicher; der Import ist nur später fertig als um 2:07 Uhr, dem frühesten Ende bei 500 Fällen pro Sekunde.

Wann ist es sinnvoll und wann nicht?

Backpressure lohnt sich

  • Eine Quelle kann deutlich schneller liefern, als die Verarbeitung schafft. Das kommt bei Datenströmen und bei nächtlichen Massenverarbeitungen wie dem Import der zwei Millionen Schadenfälle vor. Ohne Bremse wächst der Puffer jede Sekunde um die Differenz zwischen gelesenen und geprüften Fällen, und nach wenigen Minuten liegen Hunderttausende Fälle im Arbeitsspeicher. Stürzt ein Programm ab, sind die Fälle verloren, die nur im Arbeitsspeicher lagen und noch nicht verarbeitet waren. Das Problem tritt ausgerechnet dann auf, wenn am meisten Daten zu verarbeiten sind.
  • Das Lesen aus der Quelle lässt sich anhalten, ohne dass Daten verloren gehen. Liest der Importdienst eine Weile nicht weiter, geht kein Fall aus der Importdatei verloren.
  • Ein Programm ist als Kette von Stufen gebaut, die Daten nacheinander weiterreichen. Für solche Ketten gibt es Programmbibliotheken, also fertigen Code anderer Hersteller, den ein Programm einbindet. Viele dieser Programmbibliotheken haben Backpressure eingebaut. Ohne Backpressure gibt jede schnellere Stufe Daten ungebremst an die nächste weiter, und die Daten, die die langsamste Stufe noch nicht verarbeitet hat, sammeln sich an, bis der Arbeitsspeicher voll ist.
  • Das Betriebsteam soll erkennen, welche Stufe der Engpass ist. Ohne Backpressure sieht das Betriebsteam nur, dass der Arbeitsspeicher wächst, aber nicht, welche Stufe zu langsam ist. Wartet der Importdienst dauerhaft auf Anforderungen, ist der Bewertungsdienst der Engpass.

Backpressure ist nicht das Richtige

  • Die Quelle sind Kunden, die gerade eine Schadenmeldung absenden. Backpressure könnte einen Kunden nur warten lassen, bis der Empfänger wieder Meldungen anfordert, und wie lange das dauert, weiß niemand vorher. Stattdessen setzt das Team des Empfängers Rate Limiting ein. Der Empfänger nimmt dann nur eine festgelegte Zahl von Anfragen je Minute an und weist überzählige sofort ab. Der Kunde erfährt so sofort, dass er es später noch einmal versuchen muss, und seine Meldung staut sich nicht in einem Speicher, der überlaufen kann.
  • Der Import muss bis zu einer festen Uhrzeit fertig sein, und der Bewertungsdienst ist dafür grundsätzlich zu langsam. Backpressure verhindert den Speicherüberlauf, macht den Import aber nicht rechtzeitig fertig. Dann startet das Betriebsteam mehr Instanzen des Bewertungsdienstes, also weitere gleichzeitig laufende Exemplare desselben Programms. Die Instanzen teilen sich die Fälle nach dem Muster Competing Consumers. Mit vier Instanzen prüft der Bewertungsdienst bis zu 2.000 statt 500 Fälle pro Sekunde, und der Import ist nach etwa 17 Minuten fertig statt nach mehr als einer Stunde.
  • Lastspitzen sind kurz, und im Mittel holt der Empfänger mehr Daten ab, als der Sender liefert. Mit Backpressure müsste der Sender in jeder Spitze warten, obwohl der Empfänger den Rückstand kurz danach ohnehin aufholt. Stattdessen legt der Sender die Daten in eine Warteschlange, aus der der Empfänger sie abholt. Eine solche Warteschlange gehört zum Ansatz Messaging und fängt die kurzen Spitzen vollständig auf. Der Sender muss nie auf eine Anforderung des Empfängers warten. Wird die Warteschlange dauerhaft schneller gefüllt als geleert, wächst sie allerdings unbegrenzt; dann braucht es zusätzlich eine Bremse beim Sender.

Nachteile und typische Fehler

Die ganze Verarbeitung ist nur so schnell wie ihre langsamste Stufe. Backpressure macht diese Grenze sichtbar, beseitigt sie aber nicht: Der Import dauert so lange, wie die Prüfung braucht. Außerdem muss festgelegt werden, wie groß der Puffer sein darf und wie viele Fälle der Bewertungsdienst auf einmal anfordert. Ein zu kleiner Puffer lässt den Bewertungsdienst zwischen zwei Anforderungen leerlaufen, ein zu großer belegt unnötig Arbeitsspeicher. Reicht die Bremse bis zu den Nutzern einer Webseite, warten die Nutzer länger oder werden abgewiesen. Das ist oft besser als ein Absturz, muss aber bewusst entschieden und den Nutzern verständlich angezeigt werden.

Typische Fehler und woran man sie erkennt:

  • Unterbrochene Kette: Eine Stufe gibt den Gegendruck nicht an die vorherige weiter. Der Importdienst legt zum Beispiel nur angeforderte Fälle in den Puffer, liest die Importdatei aber ungebremst weiter. Die gelesenen Fälle sammeln sich dann im Arbeitsspeicher des Importdienstes an, bis der Arbeitsspeicher voll ist. Erkennbar ist das an einem stetig wachsenden Speicherverbrauch der Stufe, die den Gegendruck nicht weitergibt.
  • Unbegrenzter Puffer: Der Importdienst richtet sich nur danach, ob der Puffer noch Platz hat, und nicht nach den Anforderungen des Bewertungsdienstes. Der Puffer hat aber keine Obergrenze. Der Puffer nimmt deshalb jeden gelesenen Fall an, und der Importdienst liest ungebremst weiter. Der Puffer wächst dann jede Sekunde um die Differenz zwischen gelesenen und geprüften Fällen. Erkennbar ist das an einem Puffer, der während des Imports ständig wächst, bis der Arbeitsspeicher voll ist.
  • Verlorene Position: Der Importdienst stürzt ab, nachdem der Bewertungsdienst 800.000 Fälle geprüft hat. Ohne eigene Aufzeichnung weiß der Importdienst nach dem Neustart nicht, bis zu welchem Fall die Importdatei verarbeitet wurde. Beginnt der Importdienst wieder am Anfang der Importdatei, prüft der Bewertungsdienst diese 800.000 Fälle ein zweites Mal. Erkennbar ist das an einer Zahl geprüfter Fälle, die nicht zur Zahl der Fälle in der Importdatei passt.
  • Zu kleiner Puffer: Der Puffer fasst nur 100 Fälle, und der Bewertungsdienst fordert neue Fälle erst an, wenn er alle 100 geprüft hat. Danach wartet der Bewertungsdienst, bis der Importdienst die nächsten 100 Fälle gelesen und in den Puffer gelegt hat. Erkennbar ist das an Pausen, in denen der Bewertungsdienst keine Fälle prüft, obwohl die Importdatei noch Fälle enthält, und an einem Import, der deutlich länger dauert als die berechneten 4.000 Sekunden.

Weiterführend

Verwandte Ansätze

  • Throttling: begrenzt wie Rate Limiting, wie viele Anfragen ein Dienst von außen annimmt; bei Backpressure meldet dagegen der Empfänger selbst laufend, wie viel er gerade schafft.
  • Stream Processing: Verarbeitung fortlaufender Datenströme; solche Systeme brauchen Backpressure, damit bei hoher Last kein Speicher überläuft.

Werkzeuge und Umsetzungen

  • Project Reactor: Java-Bibliothek für Verarbeitungsketten, in der der Empfänger über eine Anforderung festlegt, wie viele Elemente er erhält.
  • RxJava: Java-Bibliothek für solche Ketten mit einem eigenen Datentyp, der Backpressure unterstützt.
  • Apache Pekko Streams: Verarbeitungsketten für Java und Scala, die die Anforderungen des Empfängers automatisch bis zur Quelle weitergeben.
  • Apache Flink: System zur Verarbeitung fortlaufender Datenströme, das vorgelagerte Stufen automatisch bremst, wenn eine nachfolgende Stufe nicht hinterherkommt.

Quellen