User Tools

Site Tools


technology:polling

Pollen

Pollen ist das regelmäßige Anfragen nach Daten, so lange, bis das gewünschte Ergebnis da ist oder man sich entscheidet, das Pollen abzubrechen. Pollen ist im Zusammenhang mit Eventual Consistency sehr nützlich. Wenn ich einen Befehl abschicke, weiß ich auch nach einem ACK1) nicht, ob das Ergebnis in den Sichten, die ich mir angucken kann, bereits vorhanden ist.

Wenn mir somit der Empfang oder die Durchführung eines Befehls bestätigt wird, habe ich im Grunde lediglich die Möglichkeit danach zu pollen, ob das Ergebnis auch schon in den Sichten angekommen ist, bevor ich mir die Sichten angucke. Die Frage ist natürlich, wonach ich polle.

Pollen nach erwarteten Ergebnissen

Wenn ich einen neuen Datensatz mit einer neuen Aggregate ID angelegt habe, und mir der Befehl2) bestätigt wurde, kann ich nach der Aggregate ID pollen, d.h. bevor ich mir eine Mitarbeiterliste angucke, kann ich solange fragen, ob der neue Mitarbeiter schon in die Liste mit eingearbeitet wurde, bis ich eine positive Antwort erhalte, bevor ich mir dann die Liste ziehe.

Der neue Mitarbeiter, bzw. die ID des neuen Mitarbeiters, ist in diesem Fall ein natürliches Polling-Kennzeichen. Es ist zwar theoretisch möglich, dass ich nach der ID frage, der Mitarbeiter noch nicht in der Liste ist. Jetzt sieht jemand anderes den Mitarbeiter, löscht ihn wieder, und die Neuanlage und das Löschen werden beide von der Liste berücksichtigt, bevor ich das nächste mal nach der ID frage. Die ID ist immer noch nicht da, aber nicht weil sie nicht angekommen ist, sondern weil sie bereits wieder gelöscht wurde. Da kann ich lange Pollen.

Diesen Fall halte ich aber nicht für dramatisch. Das Polling bricht immer nach einer definierten Zeit ab, z.B. 20 Sekunden. Wenn ich dann mitbekomme, das jemand anderes den Mitarbeiter gelöscht hat, akzeptiere ich auch das Verhalten vom System.

Was aber, wenn ich was am Mitarbeiter ändere? Dann kann ich nicht nach einer eindeutigen ID pollen. Ich brauche also was anderes:

Pollen ohne natürliches Polling-Kennzeichen

Als PollingID reicht eine GUID völlig aus3). Wenn ich nach etwas pollen möchte, setzte ich die PollingID einfach mit Guid.NewGuid()4). Das Ganze wird noch durch folgende Mechanismen sicherer gemacht:

  1. Wenn ein Denormalisierer ein Ereignis zum Denormalisieren empfängt, das eine PollingID enthält, speichert er in der Transaktion, in der er denormalisiert, auch diese PollingID, plus die AccountID des authentifizierten Kontos in einer Polling Tabelle. Dazu speichert er den Zeitstempel des Beginns der Denormalisierung. Gibt es für die AccountID/PollingiD Kombination bereits einen Eintrag, wird der Zeitstempel angepasst,
  2. Der Denormalisierer stellt sicher, dass die PollingID von dem Anwender5) eine bestimmte Zeit lang zur Verfügung steht. Da ein Client immer nur eine gewisse Zeit lang pollt, muss die garantierte Verfügbarkeitszeit lediglich mindestens so lang sein, wie die längste Polling Dauer des Clients. Ich denke, eine Stunde sollte völlig reichen.
  3. Wird eine Sicht jetzt nach einer PollingID gepollt, wird einfach ein SELECT Timestamp FROM MyPollingTable WHERE AccountID = @AccountID AND PollingID = @PollingID ausgeführt. Wenn das Ergebnis ein Ergebnis liefert mit Timestamp > Jetzt abzgl. Verfügbarkeitsgarantie, wird ein positives Ergebnis gemeldet, ansonsten ein negatives6).
  4. Ein zweiter Prozess7) räumt die alten PollingIDs auf, die älter als die garantierte Verfügbarkeitszeit sind.

> Dadurch, dass die PollingID Benutzerkonto abhängig gemacht wird, kann es nicht passieren, dass sich zwei Benutzer gegenseitig in die Quere kommen.

Über die PollingID werden immer nur bestimmte Sichten abgefragt, also auch wenn ein Anwender dieselbe PollingID für zwei Befehle verwenden sollte, ist das nur schlimm8) wenn die Befehle auch dieselbe Sicht betreffen.
Die PollingID ist ein String. Der Client könnte auch eine GUID+AggregateID+Version+Zeitstempel setzen, um das noch sicherer zu machen.
Da eine PollingID nach einer bestimmten Zeit verfällt9), ist die Wahrscheinlichkeit, dass in diesem Zeitraum10) von einem Client11) dieselbe PollingID für dieselbe Sicht erzeugt wird im Grunde null, weil die GUID Generierung auf einem Rechner eindeutig sein muss!
Auch wenn es einen Konflikt gibt, bedeutet das im schlimmsten Fall, dass das System sagt, dass ein Ergebnis sichtbar ist, obwohl es noch nicht sichtbar ist. Das wäre zwar keine besonders vertrauensbildende Benutzererfahrung, aber ja auch nicht weiter schlimm.

Lauernde Gefahren

Die PollingID kann u.U. recht wichtig werden. Ein Anwender führt ja Prozessschritte durch, ähnlich wie ein Systemagent12), und verwendet die gepollten Ergebnisse für die Entscheidung, welche Schritte als nächstes durchzuführen sind. Im besten Fall passiert folgendes:

  1. Anwender schickt Befehl ab,
  2. Anwender pollt nach Ergebnis,
  3. Ergebnis steht innerhalb des Pollingzeitraums zur Verfügung,
  4. Anwender lädt Ergebnis,
  5. Anwender trifft Entscheidung über nächste Schritte.

Eine der großen Gefahren besteht dann, wenn Sichten implizit verwendet werden um Schritte außerhalb des Systems durchzuführen. Ein Beispiel:

Der Tätigkeitsbericht in PRO•M

In PRO•M erfasst ein Mitarbeiter seine Zeiten, und druckt dann irgendwann seinen Tätigkeitsbericht aus. Dieser wird dem Kunden zur Unterschrift vorgelegt - das ist der Prozessschritt außerhalb des Systems. Die erfassten Zeiten des Mitarbeiters liegen dann einer späteren Rechnungsstellung zugrunde.

Die Gefahr

Der Mitarbeiter druckt den Bericht nachdem er alle seine Zeiten erfasst hat. Einige der Zeiten13) sind aber noch gar nicht in die Sicht denormalisiert worden. Der Kunde unterschreibt somit für 8 geleistete Stunden. Die Rechnung wird dann mit allen Zeiten erstellt, also mit allen 12 Stunden. Der Kunde hat aber nie für diese 12 Stunden unterschrieben14).

Man könnte diesen Fall lösen, indem man einige der Schritte implizit macht. Das System wäre dann zumindest schließlich konsistent. Z.B. müsste der Mitarbeiter den Tätigkeitsbericht, den er dem Kunden zur Unterschrift vorlegt, explizit erstellen. Wenn der Bericht nur 8 von 12 geleisteten Stunden enthält, dann wird das irgendwann später erkannt, weil 4 Stunden noch in keinem Bericht stehen. Diese können dem Kunden dann nachträglich vorgelegt werden.

Rechnungen werden auch nur auf erstellte Tätigkeitsberichte gestellt, nicht einfach auf erfasst Zeiten. Wenn nun ein neuer Tätigkeitsbericht mit den 4 noch nicht abgerechneten Zeiten erstellt wird, kann dieser abgerechnet werden.

So ist das System (schließlich) konsistent. Es würde lediglich inakzeptabel sein, wenn die Zeiten viel später als erwartet sichtbar sind, und vielleicht Tage nach Rechnungsstellung auftauchen.

Das System ist aktuell so gebaut, dass die Sichten sofort konsistent sind, nicht schließlich konsistent, weil sie in derselben Transaktion gebaut werden wie auch die Ausführung des Befehls. Entweder muss das so bleiben, man muss den Workflow ändern, oder man macht folgendes:

Warten auf Aktualität der Sicht

Jede Sicht wird aus Ereignissen aus einem oder mehrerer Event Stores gebaut. Die Ereignisse in einem Event Store enthalten die Ergebnisse von bisher ausgeführten Befehlen. Ich als Anwender hätte gerne die Sicherheit, dass ich für eine gewisse Aktion, die ich am dd.mm.yyyy um hh:nn:ss starte, auch die Ereignisse aller Befehle, die bis dahin ausgeführt wurden, zur Verfügung habe. Wie kann ich das machen, ohne mich auf koordinierte Uhrzeiten auf den verschiedenen Systemen zu verlassen?

Event Stores müssen zwei Dinge zusichern

  1. man kann sie nach der aktuell höchsten Ereignis ID15) fragen. Diese ist pro Event Store eindeutig.
  2. …to be continued
1) Empfangsbestätigung, oder auch Bestätigung, dass er erfolgreich durchgeführt wurde
2) Empfang, bzw. Ausführung
3) wobei ein String-Wert genommen wird, damit der Client sich entscheiden kann, vielleicht doch noch zusätzliche Dinge zu setzen, wie z.B. einen Zeitstempel
4) vielleicht noch + einen Zeitstempel
5) durch die AccountID identifizierbar
6) wenn der Timestamp länger als eine Stunde her ist, gehe ich einfach mal davon aus, dass das jetzt eine neue PollingID sein soll
7) oder Thread
8) falsche positive Antwort auf eine Pollinganfrage
9) sollte man dann vielleicht doch für Befehle einführen - wobei Event Handler Befehle auch ausführen können müssen, nachdem das System vielleicht 2 Tage nicht verfügbar war, also da lieber händisch aufräumen, sollten die Befehls-IDs zu viele werden
10) eine Stunde
11) i.d.R. ist eine Anwender auch nur mit einem Client auf einem Computer angemeldet
12) Saga
13) z.B. 4 geleistete Stunden
14) oder die Rechnung rechnet nur 6 Stunden ab, weil dort noch weniger der geleisteten Stunden angekommen sind
15) nicht nach der Sequenz, die kann auch noch NULL sein
technology/polling.txt · Last modified: 2013/01/18 19:04 by rtavassoli