Two Phase Commit
Verteilte Transaktion in zwei Runden abstimmen
Die Idee und ihr Nutzen
Eine Versicherung gibt die Auszahlung für Fall 4711 frei. Die Freigabe muss in zwei getrennten Datenbanken gespeichert werden: In Datenbank A ändert sich der Status des Vertrags, in Datenbank B entsteht ein Buchungseintrag. Die Freigabe soll nur gelten, wenn beide Änderungen gelingen. Deshalb steuert ein eigenes Programm namens Koordinator eine Abstimmung in zwei Runden. In der ersten Runde fragt der Koordinator beide Datenbanken, ob sie ihre Änderung vorbereiten und zusagen können, die Änderung auf Anweisung endgültig zu speichern. Erst wenn beide zustimmen, fordert er sie in der zweiten Runde auf, die Änderung endgültig zu speichern. Stimmt eine Datenbank nicht zu, lässt der Koordinator beide abbrechen. Dieses Verfahren heißt Two Phase Commit, auf Deutsch Zwei-Phasen-Bestätigung. Die zwei Phasen sind die beiden Runden, und Commit ist der Befehl, mit dem eine Datenbank eine Änderung endgültig speichert.
Wie funktioniert es?
Das auslösende Programm, das die Auszahlung freigibt, übergibt die Freigabe 4711 an den Koordinator, und der Koordinator beginnt den gemeinsamen Vorgang. In der ersten Runde, Phase 1 oder Vorbereitung genannt, fordert der Koordinator Datenbank A auf, den neuen Vertragsstatus vorzubereiten, und Datenbank B, den Buchungseintrag vorzubereiten. Jede Datenbank prüft, ob sie ihre Änderung speichern kann, schreibt alles Nötige dauerhaft auf ihre Festplatte und sperrt die betroffenen Datensätze, damit niemand anderes sie verändert. Dann meldet jede Datenbank dem Koordinator „bereit“. In der zweiten Runde, Phase 2 oder Entscheidung genannt, schreibt der Koordinator die Entscheidung „bestätigen“ dauerhaft in sein eigenes Protokoll, damit er sie auch nach einem eigenen Absturz noch kennt. Erst danach schickt er beiden Datenbanken den Befehl COMMIT, auf Deutsch festschreiben. Datenbank A speichert den Vertragsstatus endgültig und hebt die Sperren auf. Datenbank B speichert den Buchungseintrag endgültig und hebt ihre Sperren ebenfalls auf. Zuletzt meldet der Koordinator dem auslösenden Programm, dass die Freigabe in beiden Datenbanken gilt.
Eine Datenbank kann mehrere Änderungen in einer Transaktion bündeln: Sie speichert entweder alle Änderungen oder keine. Diese Zusage gilt aber nur innerhalb dieser einen Datenbank. Für zwei Datenbanken übernimmt deshalb der Koordinator die gemeinsame Entscheidung, und das auslösende Programm schreibt nicht selbst nacheinander in beide Datenbanken. Datenbank A und Datenbank B sind die Teilnehmer. Jede führt ihre Änderung in einer eigenen lokalen Transaktion aus. Meldet eine Datenbank „bereit“, ist das eine verbindliche Zusage: Die Datenbank hat alles Nötige dauerhaft gespeichert und kann die Änderung nun sowohl endgültig speichern als auch verwerfen. Welches von beiden geschieht, legt allein der Befehl des Koordinators fest. Selbst entscheiden darf die Datenbank ab diesem Moment nicht mehr. Teilnehmer sind meist Datenbanken oder Nachrichtensysteme, also Programme, die Nachrichten zwischen anderen Programmen zustellen. Jeder Teilnehmer muss diese vorbereitete Zwischenstufe unterstützen. Two Phase Commit, oft mit 2PC abgekürzt, ist für eine kurze technische Entscheidung gedacht, nämlich ob alle Teilnehmer ihre Änderung speichern oder alle abbrechen. Solange niemand von Hand in eine vorbereitete Transaktion eingreift, setzen beide Datenbanken dieselbe Entscheidung um.
Hat eine Datenbank in Phase 1 einen Fehler gemeldet oder nicht geantwortet, entscheidet der Koordinator „abbrechen“ und schickt beiden Datenbanken den Befehl ROLLBACK, auf Deutsch zurückrollen. Meldet zum Beispiel Datenbank B einen Fehler, weil der Buchungszeitraum bereits abgeschlossen ist, verwirft Datenbank A ihre vorbereitete Änderung, und kein Vertrag steht ohne Buchung auf „freigegeben“. Fällt der Koordinator aus, nachdem die Datenbanken „bereit“ gemeldet haben, können die Datenbanken nicht selbst entscheiden, ob sie speichern oder verwerfen sollen. Beide warten mit gesperrten Datensätzen, bis der Koordinator wieder läuft und ihnen seine gespeicherte Entscheidung mitteilt. Andere Vorgänge, die dieselben Datensätze brauchen, müssen so lange ebenfalls warten. Stürzt eine Datenbank ab, gilt ihre Zusage aus Phase 1 weiter: Nach dem Neustart findet die Datenbank die vorbereitete Änderung in ihren dauerhaft gespeicherten Daten und wartet auf COMMIT oder ROLLBACK des Koordinators. Bleibt die Antwort des Koordinators an das auslösende Programm aus, kann die Entscheidung bereits „bestätigen“ lauten. Das auslösende Programm darf eine ausbleibende Antwort nicht als Abbruch deuten.
Two-Phase Commit: erst alle bereit, dann alle festschreiben
Wann ist es sinnvoll und wann nicht?
Two Phase Commit lohnt sich
- Alle beteiligten Systeme unterstützen das Verfahren. Ein Beispiel sind zwei Datenbanken, die vorbereitete Transaktionen kennen. Ohne Two Phase Commit müsste das Entwicklungsteam für jede Änderung eine Gegenmaßnahme bauen, die eine schon gespeicherte Änderung fachlich rückgängig macht, etwa eine Gegenbuchung. Mit Two Phase Commit verwerfen die beiden Datenbanken ihre vorbereiteten Änderungen selbst, wenn der Koordinator abbricht.
- Ein Widerspruch zwischen den Datenbanken hätte fachliche Folgen. Ohne gemeinsame Abstimmung speichert das auslösende Programm die Freigabe zuerst in Datenbank A. Fällt Datenbank B danach aus, bevor der Buchungseintrag gespeichert ist, steht in Datenbank A „freigegeben“, und in Datenbank B fehlt die Buchung. Die Buchhaltung findet dann keine Buchung zu einer Freigabe, oder es wird ausgezahlt, ohne dass die Buchung stimmt. Mit Two Phase Commit findet die Buchhaltung zu jeder gültigen Freigabe den passenden Buchungseintrag.
- Die Abstimmung dauert nur einen Bruchteil einer Sekunde, weil jede Datenbank nur eine kleine Änderung vornimmt. Hier sind das der neue Vertragsstatus und der Buchungseintrag. Die Datensätze sind dann so kurz gesperrt, dass andere Vorgänge davon kaum etwas merken. Länger warten andere Vorgänge nur, wenn der Koordinator ausfällt, und diese Wartezeit ist bei einer Störung vertretbar.
- Auch nach einem Absturz muss feststellbar sein, ob die Freigabe gilt. Schreibt das auslösende Programm ohne Two Phase Commit nacheinander in beide Datenbanken und stürzt dazwischen ab, weiß danach niemand, ob der Buchungseintrag noch fehlt. Weil Koordinator und Datenbanken ihre Zusagen und Entscheidungen dauerhaft speichern, ist das möglich.
Two Phase Commit ist nicht das Richtige
- Ein fachlicher Ablauf dauert Stunden oder Tage. Mit Two Phase Commit blieben die Datensätze so lange gesperrt. Dann baut das Entwicklungsteam den Ablauf als Saga: Jede Datenbank speichert ihren Schritt sofort in einer eigenen lokalen Transaktion. Scheitert ein späterer Schritt, stößt das auslösende Programm eine Gegenmaßnahme an, die die früheren Schritte fachlich rückgängig macht, zum Beispiel eine Gegenbuchung. Keine Datenbank hält dabei Sperren über Stunden oder Tage.
- Ein beteiligtes System unterstützt das Verfahren nicht. Ein externer Zahlungsanbieter, der die Auszahlung zu Fall 4711 an den Kunden überweist, nimmt zum Beispiel lediglich einen Auftrag entgegen und kennt keine vorbereitete Zwischenstufe. Viele moderne Datenbanken und Nachrichtensysteme unterstützen das Verfahren nur eingeschränkt oder gar nicht. Auch dann baut das Entwicklungsteam den Ablauf als Saga. Die Saga braucht keine vorbereitete Zwischenstufe, sondern nur, dass jedes System eine Anfrage ausführt und eine Gegenmaßnahme annimmt, zum Beispiel eine Stornierung.
- Viele Vorgänge brauchen gleichzeitig dieselben Datensätze. Jede Störung des Koordinators hielte dann viele Vorgänge zugleich an. Auch dann baut das Entwicklungsteam den Ablauf als Saga. Jede Datenbank sperrt ihre Datensätze dann nur für die Dauer ihrer eigenen lokalen Transaktion, und keine Störung lässt Datensätze in zwei Datenbanken gesperrt zurück.
- Vertragsstatus und Buchungseintrag lassen sich in derselben Datenbank speichern. Dann speichert das auslösende Programm beide Änderungen in einer einzigen lokalen Transaktion. Der Koordinator, ein zusätzliches Programm, das ständig erreichbar sein muss, entfällt.
- Die Buchung darf kurz nach der Freigabe entstehen. Freigabe und Buchung müssen dann nicht im selben Moment gelten, und nur dafür braucht es den Koordinator. Das auslösende Programm speichert die Freigabe in Datenbank A und in derselben lokalen Transaktion eine Nachricht für das Buchungsprogramm, ebenfalls in Datenbank A. Das Buchungsprogramm ist das Programm, das die Buchungseinträge in Datenbank B anlegt. Ein weiteres Programm schickt die Nachricht danach an das Buchungsprogramm, und das Buchungsprogramm legt den Buchungseintrag in Datenbank B an. Das heißt Outbox Pattern. Koordinator und Sperren über beide Datenbanken hinweg entfallen. Dabei gibt es kurz einen Zustand, in dem die Freigabe schon gilt und die Buchung noch fehlt.
Nachteile und typische Fehler
Jeder Teilnehmer muss auf die Antwort des langsamsten Teilnehmers warten, und jede Transaktion dauert dadurch länger. Während der Abstimmung halten die Datenbanken ihre Sperren, und andere Vorgänge, die dieselben Datensätze brauchen, warten. Der Koordinator ist ein zusätzliches Programm, das ständig erreichbar sein und sein Protokoll dauerhaft speichern muss. Nach einem Absturz müssen Koordinator und Datenbanken anhand ihrer dauerhaft gespeicherten Protokolle feststellen, welche Vorgänge offen sind, und diese Vorgänge zu Ende führen. Die beteiligten Datenbanken hängen voneinander ab. Ist Datenbank B nicht erreichbar, scheitert jede Freigabe, obwohl Datenbank A läuft. Zudem muss das Betriebsteam offene, vorbereitete Transaktionen überwachen.
Typische Fehler und woran man sie erkennt:
- Blockierte Datensätze: Der Koordinator fällt nach der Meldung „bereit“ aus, und die Sperren in beiden Datenbanken bleiben bestehen, bis der Koordinator wieder läuft. Jeder andere Vorgang, der den Vertrag oder die Buchungen zu Fall 4711 ändern will, wartet so lange. Erkennbar ist das an vorbereiteten Transaktionen, die seit Minuten offen sind, und an Sachbearbeitern, die den Vertrag zu Fall 4711 nicht ändern können.
- Ausbleibende Antwort als Abbruch gedeutet: Das auslösende Programm erhält keine Antwort vom Koordinator und meldet „Freigabe gescheitert“, obwohl die Entscheidung bereits „bestätigen“ lautete. Erkennbar ist das an Fällen, die das auslösende Programm als gescheitert anzeigt, während in beiden Datenbanken die Freigabe gilt.
- Entscheidung von Hand: Der Koordinator ist nach den Meldungen „bereit“ ausgefallen, und die vorbereiteten Transaktionen zu Fall 4711 hängen. Ein Administrator will die Sperren lösen und verwirft die vorbereitete Buchung in Datenbank B von Hand. Danach läuft der Koordinator wieder und schickt seine gespeicherte Entscheidung
COMMITan beide Datenbanken. Datenbank A speichert die Freigabe endgültig, aber in Datenbank B gibt es die Buchung nicht mehr. Erkennbar ist das an einer Freigabe in Datenbank A ohne Buchung in Datenbank B nach einer Störung. - Fehlendes Protokoll des Koordinators: Der Koordinator schreibt seine Entscheidung nicht dauerhaft, bevor er
COMMITschickt. Er schicktCOMMITan Datenbank A und stürzt ab, bevor Datenbank B den Befehl erhält. Nach dem Neustart weiß der Koordinator nicht mehr, wie er entschieden hat, und Datenbank B wartet weiter mit gesperrten Datensätzen. Erkennbar ist das an offenen Vorgängen, die nach einem Neustart niemand zu Ende führen kann. - Vergessene vorbereitete Transaktion: Der Koordinator stürzt in Phase 1 ab, nachdem Datenbank A „bereit“ gemeldet hat, aber bevor er eine Entscheidung protokolliert. Nach dem Neustart findet der Koordinator den Vorgang nicht in seinem Protokoll und schickt Datenbank A weder
COMMITnochROLLBACK. Die vorbereitete Transaktion in Datenbank A wird so nie abgeschlossen. Sie hält ihre Sperren und verhindert, dass die Datenbank alten Speicherplatz freigibt. Erkennbar ist das an Einträgen in der Liste vorbereiteter Transaktionen der Datenbank, die Stunden oder Tage alt sind.
Weiterführend
Verwandte Ansätze
- Compensating Transaction: beschreibt die Gegenmaßnahme, die einen schon gespeicherten Schritt fachlich rückgängig macht. Ein Beispiel ist eine Gegenbuchung.
- Consensus: Verfahren, mit denen sich mehrere Rechner auch dann auf eine Entscheidung einigen, wenn einzelne Rechner ausfallen. Sie vermeiden das Warten auf einen einzelnen Koordinator.
Quellen
- PostgreSQL Documentation: PREPARE TRANSACTION: offizielle Dokumentation der vorbereiteten Zwischenstufe einer Datenbank; beschreibt, dass die Vorbereitung einen Absturz übersteht, und warnt, dass offene vorbereitete Transaktionen Sperren und Speicherplatz festhalten.
- Chris Richardson (microservices.io): Pattern: Saga: beschreibt die Saga als Folge lokaler Transaktionen mit Gegenmaßnahmen und erklärt, warum Two Phase Commit für Abläufe über mehrere Dienste meist keine Option ist.