tech-trends

Wir beleuchten die praktischen Probleme und theoretischen Hintergründe von Transaktionen in der Cloud.

// 22-01-2020
architektur_in_der_cloud_gepardec_blog

architektur_in_der_cloud_gepardec_blog

Warum keine globalen Transaktionen?

Transaktionen waren jahrzehntelang und sind immer noch ein zentraler Bestandteil vieler Architekturen. Verteilte Anwendungen, Microservices und Cloud-Systeme zwingen uns allerdings die Konzepte neu zu überdenken. Wir beleuchten die praktischen Probleme und theoretischen Hintergründe von Transaktionen in der Cloud.

1) Warum Transaktionen?

Transaktionen sind praktisch. Gemeinhin werden von Transaktionssystemen folgende Eigenschaften erwartet (ACID):

  • Atomarität (Abgeschlossenheit)
  • Konsistenzerhaltung
  • Isolation (Abgrenzung)
  • Dauerhaftigkeit

Wenn etwas schiefgeht kann man es rückgängig machen, alle Geschäftsregeln sind ständig erfüllt, die Arbeit erfolgt unabhängig von anderen Transaktionen und letztendlich wird alles dauerhaft gespeichert. Nachdem diese Eigenschaften das Leben der Entwickler*innen viel einfacher machen, wurden Datenbanksysteme entwickelt, die diese Eigenschaften erfüllen. Tagtäglich verwenden wir diese Systeme. Wenn wir mit transaktionalen Datenbanken (z.B. Relationale Datenbanksysteme) arbeiten, verwenden wir die Funktionen commit oder rollback, um Transaktionen zu steuern.

2) Globale Transaktionen

Die Zeiten, in denen eine Anwendung isoliert und unabhängig von anderen Anwendungen ihre Aufgaben erledigen konnte sind lange vorbei. Um das für Datenbanken erfolgreiche Transaktionskonzept auf verteilte Anwendungen und Services zwischen Anwendungen zu erweitern, wurde bereits vor ca. 30 Jahren die XA Spezifikation entwickelt.

architektur_in_der_cloud_gepardec_blog

Commits werden dabei in zwei Phasen (Zwei-Phasen-Commit) durchgeführt bei denen der eigentliche Commit nur dann ausgeführt, wenn vorher alle beteiligten Systeme erfolgreich den Prepare Befehl ausgeführt haben. Die Transaktionen werden von Transaktionsmanagern koordiniert. Der Commit kann dabei viel später und unabhängig vom eigentlichen Serviceaufruf geschehen. Neben den klassischen Transaktionssystemen wie Tuxedo, CICS oder UTM, wird die XA Spezifikation durch den JTA (Java Transaction API) Standard auch von JEE Servern wie JBoss EAP implementiert.

3) Kommunikation in Cloud Systemen

Bei der Kommunikation zwischen zwei Anwendungen werden häufig Services, z.B. Webservices, verwendet. Besonders in Cloud-Systemen wird dabei großer Wert auf Ausfallsicherheit und Skalierbarkeit gelegt. Generell sind folgende Eigenschaften wünschenswert:

  • Die Kommunikation ist eine einfache Client – Server Beziehung (Anwendung 1 als Client Anwendung 2 als Server)
  • Die Services sind stateless wodurch für Ausfallsicherheit kein Status repliziert werden muss
  • Das Programmiermodell ist weitgehend ähnlich einem lokalen Programmiermodell, also synchroner Aufruf von Funktionen.
  • Das Gesamtsystem bleibt konsistent (transaktionale Eigenschaften) bei gleichzeitig guter Performance (Skalierbarkeit) und Ausfallsicherheit (Redundanz)

Vielfach hofft man diese Eigenschaften durch Webservices und globale Transaktionen (z.B. Webservice Atomic Transaction / WS-AT) zu erhalten. Leider ist das nicht möglich.

3.1) Kommunikation mit WS-AT ist keine Client-Server Kommunikation

Wie in Abbildung 1 zu erkennen, registriert sich der Transaktionsmanager der aufgerufenen Anwendung beim Transaktionsmanager der aufrufenden Anwendung, sobald ein Service mit Transaktionsinformationen aufgerufen wird. Man hat daher eine bidirektionale Kommunikation (von beiden Richtungen werden Verbindungen aufgebaut) zwischen den Anwendungen.

3.2) Kommunikation mit WS-AT ist nicht stateless

Der Status aller aktiven Transaktionen wird vom jeweiligen Transaktionsmanager in einer lokalen Datei am Server, dem Transaktionslog, verwaltet. Dieser Status wird nicht repliziert. Informationen über die Transaktion gehen daher bei einem Serverausfall verloren. Reines Loadbalancing zur Steigerung der Verfügbarkeit ist daher nicht ohne zusätzliche Überlegungen möglich.

3.3) Ein lokales Programmiermodell kann nicht aufrechterhalten werden

Zumindest wenn man einfache Loadbalancer, wie sie in Cloud-Systemen verwendet werden, einsetzt funktionieren lokale Programmiermodelle zwischen Anwendungen mit WS-AT nicht. Als Beispiel das Anlegen eines Buchungsbeleges und das Hinzufügen einer Buchung in einer Geschäftsanwendung. Als Transaktionssystem wird JBoss EAP mit Hibernate als Datenzugriffsschicht verwendet. Die Serviceaufrufe könnten so konzipiert sein:

  • Suchen eines Buchungsbeleges
  • Wenn nicht gefunden, dann neuen Buchungsbeleg anlegen
  • Hinzufügen einer Buchung zum Buchungsbeleg

Es wird also zuerst ein Datensatz (Buchungsbeleg) angelegt und innerhalb derselben Transaktion dieser Satz beim nächsten Serviceaufruf gesucht, um eine Buchung hinzuzufügen:

architektur_in_der_cloud_gepardec_blog

Wenn diese beiden Aufrufe „Buchungsbeleg anlegen“ und „Buchung hinzufügen“ über einen Loadbalancer auf einen verschiedene Server gehen, ergeben sich zwei Probleme:
Erstens flushed Hibernate die Daten im Cache nur beim Commit, beim Lesen, wenn er „dirty“ ist, oder nach explizitem Aufruf. Dies ist bei „Buchungsbeleg anlegen“ nicht der Fall. Der neue Satz ist daher noch nicht in der Datenbank, wenn der zweite Aufruf über den anderen Server ihn lesen will. Es kommt zum Fehler.

Es wird also zumindest eine kleine Änderung (flush) in der Entwicklung benötigt. Gravierender als das obige Problem ist allerdings, dass JBoss mit WS-AT für den zweiten Aufruf einen eigenen Transaktionsbranch anlegt. Die beiden Aufrufe sind damit zwar in derselben Transaktion, aber „loosly coupled“, was zur Folge hat, dass die im ersten Service in die Datenbank eingefügten Daten im zweiten Service nicht sichtbar sind. Es kommt zum Fehler. Es ist nicht zu erwarten, dass diese Verhalten demnächst geändert wird. Damit können mit JBoss EAP und WS-AT keine feingranularen Serviceaufrufe durch globale Transaktionen verknüpft werden. Das gängige lokale Programmiermodell kann nicht verwendet werden.

3.4) Konsistenz, Performanz, und Ausfallsicherheit sind nicht gleichzeitig erreichbar

Dies ist eine etwas ungenaue Formulierung einer allgemein gültigen Eigenschaft von verteilten Systemen (Cluster) und zwar des CAP-Theorems. Es besagt, dass von den Eigenschaften Konsistenz (C), Antwortfähigkeit (A), Toleranz gegenüber Knotenausfällen (P) nur maximal zwei vollumfänglich erreichbar sind.

Die Anforderungen des Betriebes (unabhängige Deployments, Skalierbarkeit), der Benutzer (Verfügbarkeit) und der Entwicklung (Erhaltung von Konsistenz durch ein Transaktionsmodells) sind daher nicht gleichzeitig erfüllbar. Auch nicht mit WS-AT.

Bei WS-AT zeigt sich das Brewers CAP-Theorem auch am relativ hohen Kommunikationsaufwand und den damit verbundenen Performancenachteilen.

4) Konsequenzen für Cloud Architekturen

Wie wir gesehen haben, sind globale Transaktionen in Form von WS-AT nicht geeignet für Cloud Architekturen. Leider ist das nicht auf Unfähigkeit der Designer und Entwickler von WS-AT zurückzuführen.
Brewers CAP-Theorem zeigt uns, dass es sich um ein prinzipielles Problem bei verteilten Anwendungen handelt. Auch mit anderen Ansätzen, wie z.B. dem SAGA-Pattern ist die von relationalen Datenbanken bekannte starke Konsistenz (ACID) nicht erreichbar.

Wenn wir daher die Vorteile der Cloud-Systeme nützen wollen, müssen wir uns mit alternativen Programmiermodellen und alternativen Konsistenz-Modellen vertraut machen. Glücklicherweise gibt es Alternativen wie Event-Driven Architekturen, in denen strenge Konsistenz durch garantierte Zustellung ersetzt wird und ausgereifte Werkzeuge wie AMQ Messaging Broker, Vert.x oder Kafka die diese Architekturen unterstützen.

Die Herausforderung bei der Transformation in die Cloud ist es, die für die jeweilige Anforderung passende Architektur zu wählen und bestehende Denkmuster zu hinterfragen.

// Autor

Erhard