baydev
Published on

Saga Pattern

Authors
  • avatar
    Name
    Ismail Bay
    Twitter

Verteilte Daten

Eine Microservice-Architektur erfordert die Verteilung der Daten auf mehrere Services. Das gesamte System besteht aus mehreren kleinen Applikationen, die jeweils ihre Daten halten und gemeinsam für eine Funktionalität sorgen.

Dieser Ansatz bringt viele Vorteile aber auch neue Herausforderungen. Die schwierigste davon ist vermutlich, die transaktionale Natur mancher Aktionen über Services hinweg gewährleisten zu können.

Verteilte Transaktionen

Nehmen wir als Beispiel das Abgeben eines Angebotes in einem Vergabeverfahren, muss das Angebot hochgeladen und vorzugsweise in einem Dokumentenmanagementsystem abgelegt werden. Anschließend soll eine Benachrichtigung per Mail bzw. Push-Notification an die zuständige Abteilung geschickt werden.

In einer modularen Architektur spielen drei Systeme eine Rolle, die gemeinsam diese Funktionalität abbilden:

request-response

Um die Anforderung zu erfüllen, müssen alle drei Systeme ihre Aktionen erfolgreich beenden. Schlägt eine Aktion fehlt, müssen alle Services ein Rollback ausführen.

In einer Microservice-Architektur ist die Gewährleistung von ACID vermutlich die größte Herausforderung.

Two-Phase Commit (2PC)

Eine mögliche Lösung ist das Two-Phase Commit Protokoll (2PC)1, das in verteilten Systemen häufig Anwendung findet.

Die sogenannte Koordinator-Komponente steuert alle Teilnehmer und ihre Transaktionen:

  1. In der ersten Phase (Prepare) fragt der Koordinator alle teilnehmenden Systeme, ob sie bereit für einen Commit sind.
  2. Falls alle Teilnehmer positiv antworten, fordert der Koordinator in der zweiten Phase (Commit) alle Teilnehmer auf, ihre Transaktionen durchzuführen und zu bestätigen. Falls ein Teilnehmer nicht oder negativ antwortet, fordert der Koordinator alle auf, die Transaktion abzubrechen und zurückzurollen.
two-phase commit

Laut CAP-Theorem2 kann ein verteiltes System zwei der folgenden Eigenschaften gleichzeitig erfüllen:

  • Konsistenz
  • Verfügbarkeit
  • Partitionstoleranz

Wenn die Verfügbarkeit zweitrangig ist, kann 2PC eine Lösung darstellen. Z.B. in Banking-Anwendungen und Ticket-Shops soll das System konsistent sein und lieber ausfallen, als falsche Daten zu liefern.

Weiters bringt 2PC einige Nachteile mit sich:

  • Blockaden: wenn der Koordinator abstürzt, kann das gesamte System in einem unklaren Zustand steckenbleiben.
  • Single Point of Failure: der gesamte Erfolg hängt vom Koordinator ab
  • langsam: die Koordination ist aufwendig und führt naturgemäß zu einer schlechten Gesamtperformance. Der langsamste Teilnehmer hält alle anderen auf.
  • ein weiterer Nachteil ist, dass 2PC hauptsächlich von SQL-Datenbanken unterstützt wird. Im obigen Beispiel müsste man ein Mail-Server/Notification-Service einsetzen, der 2PC unterstützt.

Saga Pattern

Insbesondere in Microservice-Architekturen bietet das Saga Pattern3 eine gute Alternative zu Two-Phase Commit. Anstatt eine globale Transaktion über mehrere Services zu spannen, werden die notwendigen Aktionen in kleineren Schritten, in lokalen Transaktionen ausgeführt. In 2PC müssen die Transaktionen kurzlebig sein. Durch Sagas können auch langlebige Transaktionen abgebildet werden.

Das Saga Pattern hilft das ACID-Verhalten großteils zu erreichen:

  • Atomic: die Saga sorgt dafür, dass entweder alle lokalen Transaktionen erfolgreich sind oder im Fehlerfall Kompensationen durchgeführt werden.
  • Consistent: Soft State, das gesamte System ist schlussendlich konsistent, aber nicht jederzeit
  • Isolated: größter Schwachpunkt der Saga, mehrere parallele Sagas können unvollständige Daten lesen/schreiben
  • Durable: pro Transaktion gegeben

Typischerweise werden Sagas in zwei Varianten implementiert:

Choreografie Saga

Jedes Microservice sendet nach der lokalen Transaktion ein Event, das den nächsten Schritt in der Saga auslöst:

Saga Choreography
  • Im Idealfall sind alle Transaktionen erfolgreich
  • In Fehlerfällen werden kompensierende Aktionen ausgeführt, z.B. "Dokument verworfen"
  • das AngebotService muss auf das Event hören und die eigene Transaktion kompensieren: "Angebot storniert"

Vorteil: keine zentrale Steuerung, daher einfach in der Implementierung

Nachteil: bei hoher Anzahl beteiligter Services, steigt die Kopplung, da jedes Service den Auslöser für die eigene Transaktion und die Events für zu kompensierende Aktionen kennen muss. Zudem ist es, aufgrund der fehlenden Orchestrierung, schwer, die Zusammenhänge zu verstehen und zu modellieren.

Orchestrator Saga

In der Orchestrator-Variante werden die Aktionen von einer zentralen Saga gesteuert:

Saga Orchestrator

Der Saga-Orchestrator kann ein zentrales Service, z.B. das AngebotService, oder eine neue Komponente sein. Das sollte nach der Komplexität der Orchestrierung entschieden werden.

Vorteile:

  • kompensierende Transaktionen können leichter verwaltet werden
  • bessere Sichtbarkeit, leichteres Monitoring
  • leichtere Wartbarkeit durch lose Kopplung der Services

Nachteile:

  • der Orchestrator ist der Single Point of Failure
  • Komplexität von Services zu Orchestrator gelagert
  • Performance Bottleneck: bei vielen parallel laufenden Sagas kann des Orchestrator zu langsam werden
  • horizontale Skalierung ist nicht so leicht zu erreichen wie bei zustandslosen Services

Workflow Engine als Orchestrator

In manchen Fällen kann der Einsatz einer leichtgewichtigen Workflow-Engine als Orchestrator viele Vorteile bieten. Insbesondere bei komplexen Workflows kann die Modellierung als eine Diskussionsbasis unter allen Beteiligten (Entwickler, Fachabteilung, Kunde) dienen.

Viele Workflow-Engines kommen zudem mit Out-of-the-box Konnektoren für u.A. wie Apache Kafka, RabbitMQ oder REST Calls.

Footnotes

  1. https://en.wikipedia.org/wiki/Two-phase_commit_protocol

  2. https://de.wikipedia.org/wiki/CAP-Theorem

  3. https://microservices.io/patterns/data/saga.html