User Tools

Site Tools


technology:eventualconsistency

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.

Ich bespreche hier kurz den Unterschied zu der herkömmlichen Methode, wie ich es bisher gemacht habe.

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 angibt10), 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 bedienen11). 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. Wobei eine synchrone12) Denormalisierung das Problem nur bedingt löst. Das liegt daran, dass Transaktionen dem Anwender lediglich die Illusion von vollständiger und sofortiger Konsistenz geben. Trotzdem sollten synchrone Denormalisierungen als Sicherheit völlig ausreichen13).
  • 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,
  • Event publisher schreiben in die Events den Zeitpunkt der Veröffentlichung14). Der Denormalisierer speichert zu jeder Liste das anhand von Min(Veröffentlichungszeitpunkt) über alle Veröffentlicher, die verwendet werden, Datum und 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.
  • Die event publisher schicken zudem NoEvents Ereignisse samt Zeitstempel raus, damit die Denormalisierer den Zeitstempel aktualisieren können, auch wenn sie keinen neuen Ereignisse erhalten15).

Die letzten beiden Punkte sind für sich selbst nicht sehr aussagekräftig. Der Zeitstempel bedeutet ja nicht, dass alle Befehle bis 19:00 abgearbeitet wurden, nur, dass der Denormalisierer alle Ereignisse, die bis 19:00 veröffentlicht wurden, berücksichtgigt. Wenn ich als Anwender also einen Befehl um 18:59:59 gesendet habe, und die Sicht einen Zeitstempel von 19:00 hat, weiß ich nicht, ob der Befehl Teil der Sicht ist oder nicht. Ich weiß es sogar mit einem Zeitstempel von 20:00 oder 23:00 nicht mit Sicherheit! Ob und wie so ein Zeitstempel also umgesetzt wird muss also noch geklärt werden.

Verantwortungen beim Event Sourcing

Nach dem SRP16) 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. To be continues…

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) was sogar in den newsgroups, die mit eventual consistency gebaut sind, zu genau diesem thema passiert!!! Eigentlich ziemlich ironisch, aber leider wahr.
11) 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
12) innerhalb derselben Transaktion
13) Die echte, objektive Sicherheit ist durch die Aggregates und die Regeln, die immer eingehalten werden, gegeben. Hier geht es um das subjektive Sicherheitsgefühl des Anwenders, das nicht leiden soll
14) gemeinsam mit der lokalen, eindeutigen und lückenlosen Sequenznummer, die der Publisher den Events gibt
15) Wenn das letzte Ereignis um 17:00 kam, dann 2 Stunden kein weiteres, ist die Denormalisierung ja trotdem aktuell. Ein NoEvent Ereignis um 19:00 zeigt dem Denormalisierer an, dass nicht etwa der Publisher seinen Geist aufgegeben hat, sondern dass es wirklich nichts Neues zu berichten gab
16) Single Responsibility Principal
technology/eventualconsistency.txt · Last modified: 2013/01/19 11:19 by rtavassoli