User Tools

Site Tools


technology:eventualconsistency

This is an old revision of the document!


Eventual Consistency

Die Denormalisierer, die aus den Ereignissen, die vom Event Sourcing geliefert werden, die Projektionen bauen, greifen veröffentlichte und gespeicherte Ereignisse ab, und erstellen daraus Sichten die effizient abzufragen sind. Es kann beliebig viele solcher Denormalisierer geben. Manch komplexe Liste kann aufwendig zu bauen sein. Damit das Speichern von Ereignissen am Ende eines Befehlsaufrufs nicht durch die Denormalisierer verlangsamt wird, werden erst die Ereignisse gespeichert, und dann in einem anderen Prozess1) von den Event Handlern denormalisiert.

Das bedeutet nun, dass die denormalisierten Listen nicht transaktional mit den Ereignissen gespeichert werden. Wenn ich die Adresse einer Person ändere, und die Aktion erfolgreich durchgeführt wurde, kann es sein, dass die Adressliste, die ich gleich danach aufrufe, noch die alte Adresse anzeigt, weil die neue noch nicht vom Event Handler behandelt wurde. Die Änderung wird schlussendlich in der Liste landen, weil sie ja passiert ist, nur ist nicht klar, wie lange das dauert2). Diese Tatsache gibt eventual consistency3) seinen Namen.

Das System kann für eventual consistency gebaut werden, und eventual consistency kann durchaus als Werkzeug gesehen werden um das System verfügbar und reagierend zu halten. Angenommen ein Prozess beinhaltet diverse Schritte4). Eine Saga, die den Prozess steuert, kann alle Befehle abschicken, ohne auf das Ergebnis jedes Befehls zu warten. Sie wartet auf Ereignisse, die irgendwann als Ergebnis der Befehle zurück kommen, kann das aber asynchron machen. So kann die Saga den Prozess optimiert gestalten, und nur die Schritte seriell durchführen, die serialisiert werden müssen.

Wenn die Saga einen Befehl abschickt, erhält sie mindestens eine Empfangsbestätigung5) - wenn der Befehl synchron ausgeführt wird, kann das Ergebnis auch mehr als nur ein ACK beinhalten. Was ist mit unerwarteten Fehlern? Im einfachsten Fall bricht die Saga den Prozess ab sobald etwas unerwartetes passiert. In bestimmten Fällen kann sie den Befehl aber einfach erneut senden, so lange6) und so oft, bis sie ein ACK erhält. Wenn der Befehl in sich idempotent ist, kann die Saga ihn ohne Folgen mehrmals absenden. Ist er nicht idempotent, muss er es gemacht werden. Die Saga muss diese Idempotenz dann speichern bevor sie den Befehl das erste mal absendet, um die eventuell folgenden Befehle dann mit derselben Idempotenz zu versenden.

Die Saga wartet dann nach dem ACK auf Ereignisse, aus denen die Saga schließen kann, was das System mit dem Befehl gemacht hat7). Sie kann dann den Prozess entweder weiter führen oder abbrechen. Wenn die Saga nach einer definierten Zeit keine Ereignisse erhält, kann sie anfangen den Prozess rückgängig zu machen, zu kompensieren.

Der Anwender als Akteur

Der Anwender ist wie eine Saga ein Akteur im System. Er könnte genau wie die Saga agieren:

  • Befehl absenden - idempotent machen und so oft abschicken, bis ein ACK erhalten wurde,
  • Auf Ergebnis warten8),
  • Wenn das Ergebnis nicht nach einer gewissen Zeit da ist9), den letzten Befehl kompensieren.

Der Anwender muss somit dahingehend erzogen werden, in dem System korrekt zu agieren. Dazu muss er wissen, was es mit eventual consistency auf sich hat, und wie das System auf Befehle reagiert. Ein ungeschulter Anwender würde die Erwartung haben, dass alle Datensichten alle Aktionen berücksichtigen, die von ihm ausgelöst wurden. Mit eventual consistency muss das nicht der Fall sein.

Das kann ziemlich böse Auswirkungen haben, vor allem wenn der Anwender das anders gewohnt ist. Es kann dazu führen, dass der Anwender dem System nicht vertraut, die Änderung wieder und wieder angibt, usw. Davon auszugehen, dass alle Anwender erziehbar sind, halte ich für kritisch. Das sollte so gelöst werden, dass der Anwender so wenig wie möglich mit denken muss, ansonsten wird es immer wieder Anwender geben, die das System fehlerhaft bedienen10). Einige Möglichkeiten der Optimierung:

  • Da wo es geht, denormalisiere ich die Ereignisse transaktional in einfache Listen. Die meisten UI-Sichten können aus diesen einfachen Listen traditionell über JOINs gebaut werden und sind somit immer auf dem aktuellsten Stand,
  • Da, wo das nicht möglich ist, vor allem in komplexen Reports, wird nach dem Ergebnis des Befehls gepollt bevor das UI die Sanduhr abschaltet und die Datenaktion beendet. Wenn nun in eine Listenansicht gewechselt wird, beinhaltet die Liste die gerade gespeicherten Änderungen. Bricht das Pollen mit einem Timeout ab, wird das dem User mitgeteilt - oder aber nicht, siehe nächsten Punkt,
  • Der Denormalisierer speichert zu jeder Liste das Datum und die Uhrzeit der letzten Denormalisierung. Dieses kann in der Liste angezeigt werden, z.B. “Diese Liste enthält alle Änderungen die bis zum 09.01.2013 20:46:45 eingetragen wurden”. Wenn der Anwender weiß, dass er um 20:50 eine Änderung eingetragen hat, kann er die Liste so lange aktualisieren, bis die Listenaktualität mit dem letzten Änderungszeitpunkt übereinstimmt. In Reports sollte das genauso gemacht werden.



Eine synchrone11) Denormalisierung löst das Problem aber nur bedingt. Das liegt daran, dass Transaktionen dem Anwender lediglich die Illusion von vollständiger und sofortiger Konsistenz geben.

Verantwortungen beim Event Sourcing

Nach dem SRP12) sollte eine Klasse genau eine Aufgabe haben. Abgeleitet davon wird es oft so definiert, dass es nur genau einen Grund geben sollte, warum man die Klasse ändern müsste.

1) oder Thread
2) i.d.R. ein paar Millisekunden, wenn der Denormalisierer aber gerade nicht am Start ist, vielleicht deutlich länger
3) schlussendliche Konsistenz
4) in Form von Befehlen
5) ACK
6) bis zu einem definierten Time-Out
7) i.d.R. ob er ausgeführt wurde oder abgelehnt wurde - wurde er aus technischen Gründen abgelehnt, und nicht aus Business Gründen, kann die Saga ihn einfach nochmal abschicken
8) z.B. F5/Refresh drücken, bis die Sicht aktualisiert ist
9) wobei das System im Grunde eine Garantie abgibt, dass Befehle, deren Empfang bestätigt wurden, immer zu einem Ergebnis führen müssen; entweder einem positiven oder einem negativen
10) User ABC wurde angelegt. Anwender sieht ihn nicht in der Liste, kann ABC aber nicht erneut anlegen - weil das System ihn ja angelegt hat - also legt er ABC(1) an, vielleicht noch ABC(2). Das wäre unschön
11) innerhalb derselben Transaktion
12) Single Responsibility Principal
technology/eventualconsistency.1358263020.txt.gz · Last modified: 2013/01/15 16:17 by rtavassoli