Bulkhead
Ressourcen je Aufgabe getrennt zuteilen
Die Idee und ihr Nutzen
Der Schadendienst einer Versicherung ist ein Programm für die Bearbeitung von Schadenfällen. Für seine Arbeit ruft der Schadendienst drei andere Dienste auf: einen externen Gutachterdienst, den Zahlungsdienst und einen Adressdienst. An einem Montagmorgen antwortet der Gutachterdienst plötzlich erst nach 30 Sekunden, und immer mehr Anfragen des Schadendienstes warten auf ihn. Der Schadendienst kann aber nur 100 Anfragen gleichzeitig an andere Dienste stellen. Damit die wartenden Gutachteranfragen nicht alle 100 Plätze belegen, hat der Schadendienst für jeden der drei Dienste eine eigene Obergrenze für gleichzeitig laufende Anfragen. Ist die Grenze für den Gutachterdienst erreicht, laufen Zahlungen und Adressprüfungen trotzdem weiter. Dieses Aufteilen heißt Bulkhead, englisch für die Trennwand zwischen den Abteilungen eines Schiffsrumpfs.
Wie funktioniert es?
Der Schadendienst hat insgesamt 100 Aufrufplätze, also Plätze für gleichzeitige Anfragen an andere Dienste. Jede Anfrage an einen anderen Dienst belegt einen Aufrufplatz, bis die Antwort da ist oder die Zeitgrenze der Anfrage abgelaufen ist. Von den 100 Aufrufplätzen sind 20 für den externen Gutachterdienst reserviert. Eine solche eigene Gruppe von Aufrufplätzen heißt Pool, hier Gutachter-Pool. Das Entwicklungsteam des Schadendienstes hat für den Gutachter-Pool 20 Plätze gewählt, weil an einem normalen Tag höchstens etwa zehn Gutachteranfragen gleichzeitig laufen; die Grenze greift also nur bei einer Störung. Für Zahlungsdienst und Adressdienst gibt es im Schadendienst jeweils einen eigenen Pool, den Zahlungs-Pool und den Adress-Pool. An einem Montagmorgen hat der Gutachterdienst eine Störung und antwortet erst nach 30 Sekunden. Innerhalb weniger Sekunden sind alle 20 Plätze des Gutachter-Pools belegt. Die Sachbearbeiterin Frau Hahn fordert für einen weiteren Schaden ein Gutachten an. Die Gutachteranfrage darf nur einen Platz aus dem Gutachter-Pool belegen, und im Gutachter-Pool ist keiner frei. Der Schadendienst wartet nicht auf einen freien Platz, sondern meldet Frau Hahn sofort „Gutachten wird später angefordert“ und legt einen Auftrag zur späteren Wiederholung an. Zur selben Zeit gibt der Kollege von Frau Hahn eine Zahlung frei. Auch der Kollege ist Sachbearbeiter. Für diese Zahlung belegt der Schadendienst einen Platz im Zahlungs-Pool, in dem noch Plätze frei sind. Dann schickt der Schadendienst die Zahlung an den Zahlungsdienst. Der Zahlungsdienst antwortet in weniger als einer Sekunde mit „ausgeführt“, und der Kollege erhält die Bestätigung „Zahlung freigegeben“. Ohne getrennte Pools hätten die langsamen Gutachteranfragen alle 100 Plätze belegt, und auch diese Zahlung wäre gescheitert.
Technisch ist ein Aufrufplatz oft eine offene Netzwerkverbindung oder ein Thread, also ein Ausführungsstrang im Programm, der jeweils eine Aufgabe abarbeitet. Das Entwicklungsteam des Schadendienstes legt fest, was mit einer Anfrage geschieht, für die im zuständigen Pool kein Platz frei ist: Der Schadendienst weist sie sofort ab oder stellt sie kurz in eine kleine Warteschlange. Die Trennung lässt sich auf verschiedene Arten umsetzen: mit eigenen Pools innerhalb eines Programms, mit getrennten Netzwerkverbindungen je aufgerufenem Dienst oder mit eigenen Instanzen des Programms für bestimmte Aufgaben. Eine Instanz ist dasselbe Programm, das ein weiteres Mal eigenständig läuft. Jede Instanz hat ihre eigenen Aufrufplätze. Eine Instanz, die nur Gutachten anfordert, kann deshalb keine Plätze einer Instanz belegen, die Zahlungen ausführt. Der Bulkhead macht den Gutachterdienst nicht schneller, und der Bulkhead sorgt auch nicht dafür, dass abgewiesene Gutachteranfragen später ausgeführt werden. Dafür sorgt der Auftrag zur Wiederholung, den der Schadendienst anlegt. Andere Teile, die alle Anfragen gemeinsam nutzen, teilt der Bulkhead nicht auf. Dazu gehören der Arbeitsspeicher und die Datenbank des Schadendienstes. Das Entwicklungsteam des Schadendienstes muss prüfen, ob einer dieser Teile zum neuen Engpass werden kann.
Antwortet der Gutachterdienst wieder schnell, werden Plätze im Gutachter-Pool frei, und der Schadendienst wiederholt die zurückgestellten Gutachteranfragen. Läuft die Zeitgrenze einer Gutachteranfrage ab, kann die Gutachteranfrage beim Gutachterdienst trotzdem angekommen sein. Wiederholt der Schadendienst eine solche Gutachteranfrage später, muss der Gutachterdienst die doppelte Anforderung erkennen können. Das gelingt zum Beispiel mit einer Auftragsnummer, die bei jedem Versuch gleich bleibt.
Bulkhead: langsame Gutachten blockieren keine Zahlungen
Wann ist es sinnvoll und wann nicht?
Ein Bulkhead lohnt sich
- Ein Dienst ruft mehrere andere Dienste auf, die unterschiedlich zuverlässig sind. Der Gutachterdienst gehört einem externen Partner, auf dessen Betrieb die Versicherung keinen Einfluss hat. Zahlungsdienst und Adressdienst betreibt die Versicherung selbst. Ohne Bulkhead kann eine Störung beim Gutachterdienst alle 100 Aufrufplätze belegen, und dann scheitern auch Zahlungen, obwohl der Zahlungsdienst fehlerfrei läuft.
- Ein langsamer Dienst hat schon einmal alle Threads oder Verbindungen eines Programms belegt. Nutzen alle Anfragen dieselben 100 Aufrufplätze, belegt jede Gutachteranfrage ihren Platz 30 Sekunden statt einer halben Sekunde. Neue Gutachteranfragen kommen aber weiter im gewohnten Tempo. Nach kurzer Zeit sind alle 100 Plätze mit Anfragen belegt, die auf den Gutachterdienst warten.
- Wichtige Aufgaben sollen vor weniger wichtigen geschützt werden. Zahlungen sind wichtiger als Gutachten, weil Kunden auf ihr Geld warten. Ebenso lassen sich Zahlungen vor Berichten schützen, die der Schadendienst nebenher erstellt. Ohne Bulkhead kann ein großer Bericht mit vielen Anfragen an den Adressdienst die Aufrufplätze belegen, die eine Zahlung braucht.
- Das Betriebsteam soll auf einen Blick sehen, welcher Dienst gerade langsam antwortet. Ein voller Gutachter-Pool zeigt das an. Ohne getrennte Pools sieht das Betriebsteam nur, dass alle 100 Aufrufplätze belegt sind, aber nicht, auf welchen Dienst die Anfragen warten.
Ein Bulkhead ist nicht das Richtige
- Ein Dienst ruft nur einen einzigen anderen Dienst auf, der Schadendienst etwa nur den Gutachterdienst. Es gibt dann keine Anfragen an andere Dienste, die ein Stau beim Gutachterdienst aufhalten könnte. Stattdessen gibt das Entwicklungsteam jeder Anfrage eine Zeitgrenze, ein Timeout, und baut einen Circuit Breaker ein, der Anfragen an einen gestörten Dienst vorübergehend ganz stoppt. Pools, deren Größe eingestellt und überwacht werden muss, entfallen.
- Die vorhandenen Aufrufplätze sind so knapp, dass ungenutzte Plätze in einem Pool nicht hinnehmbar sind. Dann teilen sich alle Anfragen die Aufrufplätze, und das Entwicklungsteam begrenzt mit einer kurzen Zeitgrenze, wie lange eine Gutachteranfrage einen Platz belegen darf. Kein Platz bleibt dann ungenutzt, während andere Anfragen auf einen Platz warten.
- Ein Programm hat so wenige gleichzeitige Anfragen, dass es nie alle seine Aufrufplätze belegt, auch nicht, wenn der Gutachterdienst erst nach 30 Sekunden antwortet. Dann nutzen alle Anfragen gemeinsam die Aufrufplätze des Programms. Das Entwicklungsteam muss keine Pools einstellen, und das Betriebsteam muss keine Pools überwachen.
- Ein ganzes System soll so aufgeteilt werden, dass ein Fehler nur einen Teil der Kunden trifft. Ein Bulkhead trennt nur die Anfragen des Schadendienstes nach dem aufgerufenen Dienst. Fällt der Schadendienst selbst aus, trifft das alle Kunden. Stattdessen teilt die Versicherung das System nach der Cell Based Architecture in unabhängige Zellen auf, die jeweils einen Teil der Kunden bedienen. Ein Fehler in einer Zelle trifft dann nur die Kunden dieser Zelle.
- Die Gutachteranfragen lassen sich ohnehin später erledigen. Dann stellt der Schadendienst jede Gutachteranfrage als Nachricht in eine Warteschlange. Ein eigenes Programm holt die Nachrichten nacheinander ab und schickt sie an den Gutachterdienst. Gutachteranfragen belegen dann keine Aufrufplätze des Schadendienstes, und ein Gutachter-Pool entfällt.
Nachteile und typische Fehler
Getrennte Pools nutzen die vorhandenen Aufrufplätze schlechter aus: Der Gutachter-Pool kann voll sein, während im Zahlungs-Pool Plätze ungenutzt bleiben. Die richtige Größe jedes Pools ist schwer zu bestimmen. Sie muss anhand gemessener Werte im Betrieb festgelegt und regelmäßig überprüft werden. Jeder Pool braucht außerdem eine eigene Überwachung, damit das Betriebsteam sieht, wie voll der Pool ist und wie viele Anfragen er abweist. Mit dem Abweisen ist der Vorgang nicht beendet: Der Schadendienst muss abgewiesene Gutachteranfragen speichern und später wiederholen, und dieser zusätzliche Programmteil muss gebaut, getestet und betrieben werden. Eigene Instanzen des Programms für bestimmte Aufgaben trennen stärker als Pools in einem Programm, brauchen aber zusätzliche Server und mehr Betriebsaufwand.
Typische Fehler und woran man sie erkennt:
- Pool zu klein: Der Gutachter-Pool hat zum Beispiel nur acht Plätze, an einem normalen Tag laufen aber bis zu zehn Gutachteranfragen gleichzeitig. Der Schadendienst weist dann die neunte und die zehnte Gutachteranfrage ab, obwohl der Gutachterdienst schnell antwortet. Erkennbar ist das an abgewiesenen Anfragen bei kurzen Antwortzeiten des aufgerufenen Dienstes.
- Verlorene Aufträge: Der Schadendienst weist Gutachteranfragen ab, ohne sie zur Wiederholung zu speichern. Frau Hahn sieht nur eine unverständliche Fehlermeldung und fordert das Gutachten nicht noch einmal an. Die Gutachteranfrage geht dann verloren. Erkennbar ist das an Schäden, für die ein Gutachten angefordert wurde, aber nie eines eingegangen ist.
- Neuer Engpass: Die Pools sind getrennt, aber alle Anfragen nutzen weiterhin dieselbe Datenbank oder denselben Arbeitsspeicher. Jede der 20 wartenden Gutachteranfragen hält zum Beispiel 30 Sekunden lang eine Verbindung zur Datenbank des Schadendienstes offen. Sind dadurch alle Verbindungen zur Datenbank belegt, wartet auch die Zahlung auf eine freie Verbindung. Erkennbar ist das an langsamen Zahlungen trotz freier Plätze im Zahlungs-Pool.
- Pool ohne Zeitgrenze: Anfragen im Gutachter-Pool warten ohne Zeitgrenze auf eine Antwort. Fällt der Gutachterdienst ganz aus, werden die Plätze nie wieder frei. Auch wenn der Gutachterdienst wieder antwortet, lassen sich Gutachten erst nach einem Neustart des Schadendienstes anfordern. Erkennbar ist das an einem dauerhaft vollen Pool, obwohl der Gutachterdienst wieder antwortet.
Weiterführend
Verwandte Ansätze
- Graceful Degradation: beschreibt, wie ein System bei Teilausfällen eingeschränkt weiterarbeitet. Ein Beispiel ist der Hinweis „Gutachten wird später angefordert“ an Frau Hahn.
Werkzeuge und Umsetzungen
- Resilience4j: Java-Bibliothek mit einem eigenen Bulkhead-Baustein, wahlweise als Zähler für gleichzeitige Anfragen oder als eigener Pool von Threads.
- Polly: Bibliothek für die Plattform .NET, deren Strategie zur Begrenzung auch die Zahl gleichzeitiger Ausführungen beschränken kann.
- Envoy: ein Proxy, also ein Programm, das Verbindungen stellvertretend annimmt und weiterleitet. Envoy kann je Zieldienst die Zahl der Verbindungen und gleichzeitigen Anfragen begrenzen, ohne dass der Code des Schadendienstes geändert wird.
- Failsafe: Java-Bibliothek, die ebenfalls die Zahl gleichzeitiger Ausführungen begrenzen kann.
Quellen
- Michael Nygard: Release It! Second Edition. Design and Deploy Production-Ready Software: Buch von 2018, das Bulkheads im Kapitel über Muster für stabile Systeme neben dem Circuit Breaker beschreibt.
- Microsoft: Bulkhead pattern: beschreibt getrennte Pools je aufgerufenem Dienst, getrennte Instanzen je Nutzergruppe, die Einsatzfälle und den Nachteil der schlechteren Auslastung.