1 Punkte von GN⁺ 7 시간 전 | 1 Kommentare | Auf WhatsApp teilen
  • Dauerhafte (durable) Workflows checkpointen ihren Ausführungszustand in einer Datenbank, sodass sie nach einem Absturz ab dem zuletzt abgeschlossenen Schritt fortgesetzt werden können
  • Externe Orchestrierung im Stil von Temporal, Airflow oder AWS Step Functions erhöht die Komplexität, weil ein zentraler Orchestrator und ein zusätzlicher Datenspeicher hinzukommen
  • Eine Postgres-basierte Architektur lässt Anwendungsserver die Workflow-Tabelle pollen und Schrittausgaben direkt speichern, sodass Wiederherstellung möglich wird
  • Mehrere Server verhindern doppelte Ausführung mit Sperren und Integritäts-Constraints und adressieren Skalierbarkeits-, Verfügbarkeits- und Observability-Probleme mit Postgres-Lösungen
  • Ein einzelnes Postgres kann vertikal auf zehntausende Workflows pro Sekunde skaliert werden und Replikation, Multi-AZ und SQL-Analysen direkt nutzen

Das Grundmodell dauerhafter Workflows

  • Dauerhafte Workflows speichern den Fortschritt während der Programmausführung regelmäßig als Checkpoints in einer Datenbank, damit sie nach einem Absturz oder Fehler ab dem zuletzt abgeschlossenen Schritt wiederhergestellt werden können
  • Ähnlich wie Speichern und Laden in Videospielen lässt sich das als Modell verstehen, bei dem der Programmfortschritt regelmäßig „gespeichert“ und nach einem Absturz vom letzten Checkpoint wieder „geladen“ wird
  • Übliche Implementierungen sind Systeme zur externen Orchestrierung wie Temporal, Airflow oder AWS Step Functions
  • Bei externer Orchestrierung werden dauerhafte Programme als Workflows mit mehreren Schritten geschrieben, und ein zentraler Orchestrator koordiniert die Ausführung

So funktioniert externe Orchestrierung

  • Wenn ein Client einen Workflow einreicht, erstellt der Orchestrator einen Datensatz im Datenspeicher und dispatcht ihn zur Ausführung an einen Worker
  • Immer wenn der Worker einen Schritt abschließt, sendet er das Ergebnis an den Orchestrator, der die Ausgabe im Datenspeicher checkpointet und dann den nächsten Schritt dispatcht
  • Wenn der Worker abstürzt oder ausfällt, dispatcht der Orchestrator den betreffenden Workflow erneut an einen anderen Worker, der beim letzten checkpointeten Schritt beginnt
  • Diese Struktur erhöht die Komplexität, weil sie zur Kernidee, den Workflow-Zustand in einer Datenbank zu speichern, noch einen separaten Orchestrator-Server hinzufügt

Architektur mit Postgres als Orchestrator

  • Wenn der Kern dauerhafter Workflows darin besteht, Programmzustand in einer Datenbank zu checkpointen, ist es einfacher und effizienter, die Datenbank selbst als Orchestrator zu verwenden statt eines separaten Orchestrator-Servers
  • Postgres ist dank seiner Popularität, Skalierbarkeit und seines reichhaltigen Ökosystems eine geeignete Wahl für den Aufbau dauerhafter Workflows
  • In einem Postgres-basierten System für dauerhafte Workflows kommunizieren Anwendungsserver direkt mit Postgres, um Workflows auszuführen, ohne einen zentralen Orchestrator dazwischen
  • Der Client reicht die Ausführung ein, indem er einen Eintrag in der Workflow-Tabelle von Postgres anlegt, und die Anwendungsserver pollen diese Tabelle, dequeuen Workflows und führen sie aus
  • Während der Ausführung checkpointen die Server die Ausgabe jedes Schritts in Postgres; wenn der ausführende Server abstürzt oder ausfällt, stellt ein anderer Server den Workflow vom Checkpoint aus wieder her

Warum kein zentraler Orchestrator mehr nötig ist

  • Anwendungsserver können sich über Postgres koordinieren, sodass kein zentraler Orchestrator Workflows an Worker dispatchen muss
  • Die Server dequeuen Workflows kooperativ aus Postgres-Tabellen, und Mechanismen wie Sperrklauseln können sicherstellen, dass jeder Workflow genau von einem Worker dequeued wird
  • Auch Schrittausgaben werden nicht vom Orchestrator, sondern direkt vom Worker in Postgres geschrieben
  • Wenn mehrere Worker versuchen, denselben Workflow gleichzeitig auszuführen, können die Integritäts-Constraints von Postgres doppelte Arbeit beim Checkpointing erkennen und einen der Worker zurückziehen lassen
  • Ersetzt man den zentralen Orchestrator durch Postgres oder eine andere Datenbank, lassen sich Probleme rund um Skalierbarkeit, Verfügbarkeit, Observability und Sicherheit mit gut bekannten Postgres-nativen Lösungen angehen

Skalierbarkeit und Verfügbarkeit

  • Skalierbarkeit und Verfügbarkeit eines datenbankbasierten Systems für dauerhafte Workflows werden grundsätzlich von der zugrunde liegenden Datenbank bestimmt
  • Da sich horizontal skalieren lässt, indem mehr Worker-Server hinzugefügt werden, hängt die maximale Kapazität davon ab, wie schnell die Datenbank Workflows verarbeiten kann
  • Worker sind austauschbar und können gegenseitig ihren Zustand wiederherstellen, daher bleibt das System verfügbar, solange die zugrunde liegende Datenbank verfügbar ist
  • Ein Vorteil von Postgres ist, dass Skalierbarkeit und Verfügbarkeit dort seit Langem erforschte Probleme mit robusten Lösungen sind
  • Ein einzelner Postgres-Server kann vertikal skaliert werden, um zehntausende Workflows pro Sekunde zu verarbeiten
  • Weitere Skalierung lässt sich mit verteiltem Postgres oder geshardetem Postgres wie CockroachDB erreichen
  • Postgres unterstützt Streaming-Replikation mit automatischem Failover, und Managed Services bieten standardmäßig Multi-AZ-Deployments und Hochverfügbarkeits-SLAs
  • Jahrzehntelang aufgebaute Engineering-Erfahrung und Forschung für den Betrieb von Postgres im großen Maßstab lassen sich direkt auch für den Betrieb dauerhafter Workflows nutzen

Observability

  • Bei Postgres-basierter dauerhafter Ausführung werden Workflows und Schritte in Postgres-Tabellen checkpointet, sodass sich diese Checkpoints scannen lassen, um Workflows in Echtzeit zu überwachen und Ausführungen zu visualisieren
  • Eine Stärke von Postgres ist, dass sich nahezu jede Observability-Abfrage für Workflows in SQL ausdrücken lässt
  • Komplexe Filter- und Analyseaufgaben lassen sich deklarativ in SQL formulieren, etwa eine Abfrage, die alle Workflows mit Fehlern im letzten Monat findet
  • Möglich ist das durch das relationale Modell von Postgres und Jahrzehnte an Forschung zur Abfrageoptimierung
  • Viele Systeme mit einfacheren Datenmodellen, etwa Key-Value-Stores wie sie beliebte externe Orchestratoren verwenden, bieten nicht dieselbe Unterstützung für SQL-basierte Analysen
  • Wenn Workflow- und Schrittdaten in Postgres-Tabellen gespeichert und zusätzliche Indizes für schnelle analytische Abfragen angelegt werden, erhält man effiziente Observability für dauerhafte Ausführung

Zuverlässigkeit und Sicherheit

  • Bei dauerhafter Ausführung mit externem Orchestrator werden sowohl der Orchestrator als auch sein Datenspeicher zu Single Points of Failure
  • Da Orchestrator und Datenspeicher die Workflow-Ausführung direkt koordinieren, wird die gesamte Anwendung unbenutzbar, wenn einer von beiden ausfällt
  • Da sie Workflow- und Schritt-Checkpoints verarbeiten und speichern, können sie Zugriff auf sensible Anwendungsdaten haben und müssen wie sensible Infrastruktur gehärtet, zugriffskontrolliert und auditiert werden
  • Bei Postgres-basierter dauerhafter Ausführung ist der einzige Ausfallpunkt Postgres selbst, und alle Workflow-Daten werden direkt in Postgres gespeichert, ohne andere Systeme zu durchlaufen
  • Wenn die Anwendung ohnehin bereits von Postgres abhängt, fügt die Einführung dauerhafter Ausführung keinen neuen Ausfallpunkt hinzu und schafft auch keine zusätzliche Angriffsfläche, die geschützt werden muss
  • Da die Datenbank bereits kritische Infrastruktur ist, ist es sinnvoller, die bestehende Datenbank wiederzuverwenden, statt für Orchestrierung neue kritische Infrastruktur hinzuzufügen

Mehr erfahren

1 Kommentare

 
GN⁺ 7 시간 전
Hacker-News-Kommentare
  • Armin Ronachers absurd ist eine Implementierung dauerhafter Workflows für Postgres
    https://lucumr.pocoo.org/2025/11/3/absurd-workflows/
    https://github.com/earendil-works/absurd
    https://earendil-works.github.io/absurd/
    Ich habe es selbst noch nicht ausprobiert, aber es scheint sich zu lohnen, es mit anderen Optionen zu vergleichen

    • Wenn man keinen sehr hohen Durchsatz braucht, sind absurd und die davon abgeleitete Rust-Implementierung durable meiner Meinung nach gute Optionen, um die Client-Seite sehr einfach zu halten
      Es ist leichtgewichtig, sodass ein Coding-Agent die Gesamtstruktur leicht im Kopf behalten kann, und falls nötig kann man den Status per Query abfragen
  • Ich nutze dbos.dev, restate.dev und cf workflows, und in unserer Agents.md steht dazu Folgendes
    Restate.dev verwenden wir für die Zahlungsintegration von northflank. Es ist schneller als cf workflows, unabhängig von Cloudflare und dessen Ausfällen, kann selbst gehostet werden und vermeidet damit Vendor Lock-in
    Cloudflare workflows verwenden wir für weniger kritische Aufgaben wie die Erstellung von CSV-/PDF-Berichten. Es ist einfach sehr günstig
    DBOS.dev verwenden wir für Workflows, die atomisches Messaging gekoppelt an Postgres-Transaktionen benötigen und deshalb 100% Zuverlässigkeit/Langlebigkeit erfordern. Zum Beispiel das Befüllen materialisierter Zeilen oder das Versenden geschäftskritischer E-Mails/Push-Nachrichten an Händler
    DBOS und Restate wirken auf den ersten Blick ähnlich, aber Restate benötigt einen zentralen „Orchestrator“, was Vor- und Nachteile hat, dafür lässt es sich leichter zusammen mit serverlosen Workern von cf/vercel aufbauen
    Außerdem gibt es VirtualObject, was als Vendor-Lock-in-freie Open-Source-Alternative zu Cloudflares single-threaded DurableObject ganz brauchbar ist
    Besonders stark ist DBOS an zwei Punkten. 1) Mit dbos.enqueue_workflow kann man atomisches Messaging innerhalb derselben DB-Transaktion wie die Business-Logik durchführen. Das ist bei vielen Ansätzen oft der fragilste Teil, und wenn es atomisch und dauerhaft in derselben Transaktion abgewickelt wird, in der die Business-Logik läuft, sinkt die Komplexität erheblich
    2) DBOS speichert den Workflow-Status in der DB, daher dürfte es leicht sein, Observability-Dashboards mit metabase/looker zu bauen. Schön wäre, wenn Restate auch seine rocksdb-Instanz exponieren würde, damit man sie mit metabase verbinden kann

    • Ich frage mich, wie Schema-Updates gehandhabt werden. Migriert ihr laufende Jobs, oder behandelt ihr Worker-Deployments auf eine bestimmte Weise?
  • Mich würde interessieren, wie sich DBOS und Temporal für Leute anfühlen, die beides ausprobiert haben.
    Ich habe früher Temporal verwendet, und es hat ziemlich gut funktioniert, aber die Größenbeschränkungen bei Request-Payloads und Events waren beim Entwickeln von Lösungen manchmal lästig.
    Es hat zwar den Vorteil, gute Engineering-Praktiken zu erzwingen, aber ich möchte nicht immer spezielle Logik verwenden müssen, bei der ich eine CSV-Datei, nur weil sie größer als 2 MB ist, nach S3 hochlade, einen Link weiterreiche und sie im Workflow wieder herunterlade.
    Mich würde interessieren, wie die Erfahrung mit DBOS ist und wie es sich in Betriebsaufwand oder Funktionsgleichheit mit Temporal vergleichen lässt.

    • Ich habe DBOS nicht benutzt, aber in meinem aktuellen und meinem vorherigen Job mit Temporal gearbeitet, insgesamt etwa 1,5 Jahre.
      Ich betreibe es auch zu Hause, um Heimautomatisierungsaufgaben zu verarbeiten, bei denen die Zeitsensitivität nicht allzu hoch ist. Die Workflow-Latenz ist nicht besonders schlecht, aber für Trigger, die sofort reagieren müssen, wie Bewegungserkennungsereignisse im Haus, würde ich es wohl nicht einsetzen; für Timeouts wie „etwas nach Inaktivität ausschalten“ ist es aber okay.
      Ich mag den Ansatz sehr, vor Temporal innerhalb eines VPCs oder Kubernetes-Clusters eine schlanke REST API zu setzen. So müssen sich ereignisbasierte Trigger nicht um Temporal-Authentifizierung oder die Prüfung des Workflow-Status kümmern, und es hilft, die Events selbst möglichst frei von Logik zu halten.
      Zum Beispiel wird ein DB-Trigger direkt ausgelöst oder legt ein Event in eine Queue, und ein Handler ruft mit den benötigten Event-Details die schlanke REST API auf. Die REST API kann dann entscheiden, ob sie einen Workflow starten, an einen bestehenden Workflow ein Signal senden oder das Ganze ignorieren soll. Das Muster variiert je nach Situation, aber ich verwende häufig SignalWithStart, oder ich verwerfe es einfach, wenn sich ein Start nicht lohnt und es auch keinen bestehenden Workflow gibt.
      Außerdem ist die Funktion Parent-/Child-Workflows sehr nützlich, wenn man voneinander unabhängige Aktionen innerhalb des Lebenszyklus eines einzelnen Objekts orchestrieren muss, und ich finde auch gut, dass Abbrüche möglich sind, wenn externe Faktoren den Verlauf eines Objekts ändern.
      Lang und vage gesagt: Es ist sehr leistungsfähig, gut handhabbar und äußerst hilfreich, um Lebenszykluslogik aus der API herauszuziehen. Wenn man sie in der API belässt, sammeln sich leicht technische Schulden an und die Wartbarkeit wird fragil. Ich stimme zu, dass es besser ist, zu guten Praktiken gezwungen zu werden, als Logik an scheinbar naheliegende Stellen zu werfen, die sich später als versteckte Falle entpuppen.
    • Ich fand Temporal übermäßig komplex, aber wie gesagt ist der beste Teil, dass es gute Engineering-Praktiken erzwingt.
      Dann habe ich aber das Cloud-Produkt ausprobiert und war über den Preis schockiert. Noch bevor ich es in Produktion genommen hatte, waren bereits 1.000 Dollar an kostenlosen Credits aufgebraucht. Lokal selbst Temporal zu betreiben wollte ich auch nicht.
      Meiner Meinung nach ist es am besten, nur die Ideen aus der Architektur zu übernehmen und das Ganze direkt mit Postgres selbst umzusetzen.
    • Um das Problem großer Payloads zu lösen, haben sie gerade den External-Storage-Ansatz veröffentlicht.
      Ich bin damit nicht zu 100 % zufrieden. Es wirkt eher angeklebt als wie ein wesentlicher Bestandteil, und es ist noch ein früher Release. Trotzdem kann man das Problem jetzt im Grunde als gelöst betrachten.
    • Ich betreibe eine große On-Premises-Temporal-Installation, und dieser Account ist ein Wegwerfaccount, damit ich nicht erkannt werde.
      Nach über einem Jahr Betrieb in Produktion kann ich sagen: Temporal ist schlecht designt, langsam und aus Infrastruktursicht absurd schwergewichtig.
      Schon bei nicht ganz trivialen Aufgaben, zum Beispiel mehr als 200 Events pro Workflow und nur ein paar hundert gleichzeitig laufenden Workflows über den ganzen Tag, kann man Millionen Dollar an Infrastrukturkosten ausgeben, und es ist immer noch nicht besonders gut.
      Wenn man eigene Benchmarks fährt, sind die Zahlen miserabel.
      Auch das Vertriebsteam ist wirklich furchtbar und wirkt verzweifelt.
      Aus Entwicklersicht ist das SDK ziemlich gut.
      Lass dich nicht in nexus einsperren, und wenn der Vertrieb anruft, sollte unbedingt die Rechtsabteilung mit im Raum sitzen.
    • Wir verwenden dbos für KI-generierte Workflows und die Verarbeitung von Videodateien.
      Es hat eine Weile gedauert zu verstehen, wie wir von Celery migrieren, aber in unserem Fall war es das wert.
  • Conductor OSS kann das ebenfalls ziemlich gut https://docs.conductor-oss.org/devguide/ai/index.html
    https://github.com/agentspan-ai/agentspan ist im Wesentlichen eine Agent-SDK-Schicht für Conductor, mit der sich langgraph-, OpenAI-, vercel- und ADK-Agenten ohne Codeänderungen langlebig machen und zusätzlich orchestrieren lassen.

    • In unserer Produktion verwenden wir Redis als Queue, aber ich habe auch Nutzer gesehen, die Postgres und MySQL als Queue einsetzen.
  • Statt Datenspeicher, Zustandsmaschine, gültige Zustandsrestriktionen und die Logik für Übergänge zwischen gültigen Zuständen zu trennen, wäre es gut, wenn man all das in eine Art Kern des App-Zustands integrieren könnte.
    Ehrlich gesagt bringt Postgres schon viele dieser Fähigkeiten mit, aber auf App- oder Produktebene sehe ich noch kein klares Bild, das eine beweisbare Menge von Zuständen liefert, in die eine App übergehen kann, und diese automatisch auf eine für Clients nützliche Weise sichtbar macht. Zum Beispiel: Dieser Nutzer darf diesen Beitrag liken, aber nicht bearbeiten.
    Für mich sieht das nach einer Form von Colored Petri Nets aus, aber ein einfaches App-State-Paradigma, bei dem die Datenbank ähnlich klar definierte Erfolgsgrenzen hat, sehe ich noch nicht.

    • Temporal.io kommt mit Query-, Signal- und Update-Funktionen dem teilweise nahe.
      Ich bin mir nur nicht sicher, ob das eine vollständige Integration ist.
    • Es hat Versuche in diese Richtung gegeben, aber tausendzeilige Stored Procedures sind wirklich ein Albtraum.
    • Das klingt nach convex.dev oder https://spacetimedb.com/. Ich nutze aber keines von beiden selbst.
  • Weil DBOS Rust nicht unterstützt, habe ich unter https://github.com/tensorzero/durable eine sehr minimale Rust-Version von etwas Ähnlichem implementiert.
    Das war ziemlich stabil und skalierbar, aber natürlich muss man bei der SQL-Implementierung sehr vorsichtig sein. Ich hoffe, das ist für die Leser hier interessant.

  • Das Konzept verstehe ich vollständig und stimme ihm zu. Es ist eine hervorragende Methode, diese Art von Dauerhaftigkeit in ein Workflow-System einzubauen.
    Mit meinem Gamer-Gehirn würde ich das allerdings als groß angelegtes Save Scumming bezeichnen. Viele wissen vermutlich bereits, dass dieser Ansatz funktioniert, haben ihn aber vielleicht nicht mit abstrakten informatischen Konzepten verknüpft.
    Eine weitere Strategie zur Erhöhung der Robustheit besteht darin, Workflows aus idempotenten Operationen aufzubauen. Das kann nützlich sein, wenn der Workflow-Zustand zu groß ist, um ihn leicht zu sichern. Stattdessen führt man den Job einfach von Anfang an erneut aus, und bis zu dem Punkt, an dem wieder Fortschritt entsteht, ist alles ein No-Op.

  • Es erstaunt mich immer wieder, wie viel man mit wenigen Werkzeugen machen kann, solange Postgres im Werkzeugkasten ist.
    Ich habe vor Kurzem eine verteilte Queue entwickelt, die wirklich gut funktioniert, gute Benchmarks zeigt und weder Race Conditions noch Konflikte hat. Ich habe SKIP LOCKED verwendet, damit Worker sicher miteinander konkurrieren können.
    Wenn man Worker über mehrere Nodes hinweg Kollisionen vermeiden lassen will, kann man auch Session-skopierte Mutexes verwenden, also pg advisory lock.

    • In so einem Fall werden advisory locks ohnehin bevorzugt, weil es nicht gut skaliert, viele SELECT FOR UPDATE-Locks zu halten.
      Bearbeitung: Beim erneuten Nachsehen scheint der Rat inzwischen eher in die entgegengesetzte Richtung zu gehen.
    • Man kann den Datensatz auch einfach mit einer Actor-ID reservieren.
  • In Rails gibt es mehrere datenbankbasierte Job-Backends, aber der Konvention nach sollte ein Job immer nur eine einzige Sache tun und möglichst sehr schnell abgeschlossen sein.
    Dadurch wirkt das Bauen von Workflows etwas erzwungen. Man landet dann dabei, am Ende des ersten Jobs den zweiten in die Queue zu stellen und am Ende des zweiten den dritten.
    Das Job-Backend stellt diese nicht als verbundenen Workflow dar, sondern behandelt sie als unabhängige Jobs, und wenn man den Workflow auf einer höheren Ebene verstehen will, muss man mehrere Job-Klassen lesen.
    Rails hat kürzlich das Konzept continuable eingeführt, mit dem man innerhalb eines Jobs schrittweise Checkpoints setzen und fortsetzen kann, aber die Konvention, Jobs bei einer einzigen Verantwortung zu halten, ist immer noch stark, sodass es sich für echte Workflows etwas unnatürlich anfühlt.
    Mich würde interessieren, ob andere auf dasselbe gestoßen sind und ob sie eine Lösung gefunden haben.

  • Das ist ein hervorragendes Muster. Es ist gut, so viel wie möglich innerhalb der Datenbank zu erledigen.
    Externes Spanner bietet Change Streams an. Internes Spanner ist anders; das liegt vor allem daran, dass es in manchen Fällen extreme Skalierungsanforderungen gibt, aber auch an einer Mischung aus „es läuft bereits gut so“ und „beliebige Change Streams sind beängstigend“.
    Internes Spanner erlaubt es jeder Transaktion, Queue-Einträge zu schreiben. Die Queue ist dabei ungefähr eine Tabelle mit speziellem Zeitverständnis. Man kann die Zustellung planen, Einträge werden aus der Queue an Handler gepusht, und die Handler können innerhalb der Dequeue-Transaktion ebenfalls in die DB schreiben. Und dieselbe Skalierbarkeit bleibt vollständig erhalten.