3 Punkte von GN⁺ 4 시간 전 | 1 Kommentare | Auf WhatsApp teilen
  • Eine Erweiterung für durable functions, die Retries, Scheduling, paralleles Fan-out und bedingte Verzweigungen innerhalb von PostgreSQL allein mit einer kleinen SQL-DSL verarbeitet
  • Läuft ohne Container oder externe Services nur mit Postgres und Background Workern
  • Alle Schritte schreiben ihren Status als Checkpoints in PostgreSQL, sodass sie nach Crash, Neustart oder Verbindungsabbruch ab dem Unterbrechungspunkt fortgesetzt werden
  • Statt Queue-Management, Statusverfolgung, Crash-Recovery, Schrittkoordination und Retries selbst zu implementieren, schreibt man nur SQL, den Rest übernimmt die Orchestration Engine
  • Ersetzt Aufgaben, die bei Eigenimplementierung mehr als 300 Zeilen Boilerplate erfordern würden, durch einen einzelnen DSL-Aufruf und ist mit PostgreSQL 17 sofort als Open Source nutzbar

Überblick und zentraler Nutzen

  • Eine in Postgres integrierte crash-proof durable function, die Retries, Scheduling, paralleles Fan-out und bedingte Verzweigungen mit einer kleinen SQL-DSL orchestriert
  • Funktioniert ohne zusätzliche Infrastruktur nur mit Postgres + Background Worker, separate Container oder externe Services sind nicht nötig
  • Übernimmt als Orchestration Engine Queue-Management, Statusverfolgung, Crash-Recovery, Schrittkoordination und Retries; der Nutzer schreibt nur SQL

Implementierung ohne pg_durable

  • Wer 3 Aggregationen parallel ausführen, anschließend ein Dashboard aktualisieren und dabei auch Retries und Crash-Recovery einbauen will, braucht mehr als 300 Zeilen Boilerplate
    • Was man selbst bauen muss: Queue-Setup und -Konfiguration, Worker-Management und Polling, Nachrichtenverarbeitung und Statusverfolgung, Fehlerbehandlung und Retries, manuelle Schrittkoordination
    • Der Beispielcode enthält zahlreiche Status-Tabellen wie job_queue, job_results, job_state, workflow_steps, step_variables, scheduled_jobs sowie Polling-Worker, Workflow-Fortschritt, Crash-Recovery, einen Koordinator für parallele Ausführung, Variablenweitergabe, Scheduling und Cleanup-Funktionen
    • Für die Berechnung von next_run im Scheduling wird zusätzlich eine externe Cron-Parser-Bibliothek benötigt

Implementierung mit pg_durable

  • Dieselbe parallele Aggregation plus Dashboard-Aktualisierung lässt sich mit einem einzigen Aufruf von df.start() ausdrücken; mit dem &-Operator erfolgt das Fan-out, mit ~> der Join
    • Beispiel: Drei Queries verzweigen parallel und laufen anschließend im Schritt refresh dashboard wieder zusammen, um das Ergebnis zu erzeugen
    • Im Live-Beispiel ist nach 3 parallel ausgeführten Schritten samt Join bis zu dashboard ready alles durable in nur 1,9 Sekunden abgeschlossen
  • Queue-Management, Statusverfolgung, Crash-Recovery, Schrittkoordination und Retries übernimmt vollständig pg_durable

Wichtige Merkmale

  • Standardmäßig durable

    • Jeder Schritt schreibt seinen Status als Checkpoint in PostgreSQL, sodass der Workflow auch bei Crash, Neustart oder Verbindungsabbruch überlebt
    • Die Ausführung wird exakt an der Unterbrechungsstelle fortgesetzt
  • Automatische Retries

    • Eingebaute Retry-Logik für fehleranfällige Aufgaben; bei einem Fehlschlag wird nur der betroffene Schritt erneut ausgeführt, der restliche Workflow läuft weiter
    • Manueller Code für Fehlerbehandlung ist nicht nötig
  • Volle Observability in SQL

    • Sämtliche Workflow-Zustände werden in Postgres-Tabellen gespeichert; Ausführungshistorie, Schritt-Outputs und Fehleranalyse lassen sich mit Standard-SQL abfragen
    • Externe Dashboards sind nicht nötig
  • Parallele Ausführung

    • Mit dem &-Operator oder df.join() lassen sich unabhängige Aufgaben per Fan-out verzweigen; Aggregationen, API-Aufrufe oder ETL-Schritte werden mit automatischer Koordination parallel ausgeführt

Muster, die sich damit umsetzen lassen

  • ETL-Pipelines

    • cleanup → transform → load lässt sich mit garantierter Reihenfolge verknüpfen; jeder Schritt wartet auf den vorherigen, und bei Fehlern stoppt die Pipeline sauber (~> sequence, |=> variables)
  • Parallele Aggregation

    • Nutzerzählung, Umsatzsummierung und Bestandsprüfung laufen gleichzeitig; per Fan-out über mehrere Queries und mit Warten auf den Gesamtabschluss (&, df.join())
  • Auftragsverarbeitung

    • Eine Bestell-ID wird erfasst und an die Schritte Validierung, Verarbeitung und Abschluss weitergereicht; Variablen fließen automatisch zwischen den Schritten (|=> capture, $var substitution, df.sleep())
  • Geplante Jobs

    • API-Polling, Datensatzarchivierung und Datensynchronisierung per Cron-Schedule; die Schleife läuft dauerhaft weiter und überlebt auch Neustarts (@> loop, df.wait_for_schedule())
  • Bedingte Verzweigungen

    • Offene Aufgaben, Zeilenzahl oder Flags werden geprüft, um zu verarbeiten oder zu überspringen; die Verzweigungslogik liegt in SQL statt in der Anwendung (df.if(), ?> conditional)
  • Mehrstufige Validierung

    • Daten abrufen → Schema validieren → Business-Regeln prüfen → genehmigen/ablehnen; jeder Schritt wird checkpointed, sodass selbst bei Fehlern kein Fortschritt verloren geht
  • Datenbankwartung

    • Blocker für autovacuum, Table Bloat und Wraparound-Risiken werden erkannt und zur Prüfung bereitgestellt; nach Freigabe erfolgt die Korrektur auch über Neustarts hinweg durable (?> conditional, df.wait_for_signal(), @> loop)
  • Azure Functions & HTTP

    • Mit df.http() lassen sich Azure Functions oder erlaubte HTTPS-Endpunkte direkt aus SQL aufrufen, um Dokument-Chunking, Zeilenanreicherung oder Datensatzklassifizierung inline auszuführen
  • Human-in-the-Loop-Freigabe

    • Alltägliche Aufgaben werden automatisch genehmigt, während risikoreiche Vorgänge wie große Rechnungen oder destruktive Operationen bis zu einem menschlichen Freigabesignal pausieren (df.wait_for_signal(), df.if())

KI-gestützte Schreibhilfe

  • Beschreibt man den Workflow in einfachem Englisch, erzeugt Copilot das korrekte durable-function-SQL; man muss also nicht erst die Syntax lernen, sondern nur das gewünschte Verhalten formulieren
  • Das Repository enthält den wiederverwendbaren Agent-Skill pg-durable-sql, der GitHub Copilot und anderen Agenten beibringt, korrektes SQL mit Operatoren, Variablenersetzung, Schleifen und parallelen Joins zu erzeugen

Als Open Source verfügbar

  • Vollständig als Open Source verfügbar – ohne Warteliste und ohne Lock-in; Repository klonen, bauen und sofort im eigenen PostgreSQL ausführen
  • Durable Orchestration lässt sich auf Notebook, Server oder in der Cloud einsetzen

Managed-Option Azure HorizonDB

  • Azure HorizonDB ist Microsofts neuer PostgreSQL-Cloud-Service mit integriertem pg_durable, sodass geschriebene durable functions erhalten bleiben und zugleich Enterprise-Skalierung, Sicherheit und zusätzliche KI-Funktionen hinzukommen
    • Bis zu 3× höhere Performance, automatische Speicher-Skalierung bis 128 TB, Compute-Scale-out bis 3.072 vCore
    • Microsoft Defender zur Echtzeit-Bedrohungserkennung, Microsoft Entra ID für Identitätsmanagement
    • Filtered DiskANN-Vektorsuche, semantisches Ranking und In-Database-Kuratierung von KI-Modellen
    • Microsoft Fabric mit Beinahe-Echtzeit-Mirroring, VS-Code-Integration und Anbindung an GitHub Copilot
  • Integrierte KI-Pipeline

    • HorizonDB schichtet auf die durable Ausführung von pg_durable eine gemanagte End-to-End-KI-Pipeline, bei der jeder Schritt Checkpoints, Retries und Crash-Sicherheit bietet
    • Ablauf der Schritte: Ingest (Dokumente/Daten laden) → Chunk (Inhalte aufteilen) → Embed (vektorisieren) → Index (in DiskANN speichern) → Serve (Suche/Ranking)

1 Kommentare

 
GN⁺ 4 시간 전
Hacker-News-Kommentare
  • 2026 wird wohl das Jahr der Postgres-Queues: Es gibt Entwicklungen wie DBOS[0] und pgQue[1], und es ist großartig, dass die Community solche Optionen schafft
    Aus Sicht eines ehemaligen Application Engineers bevorzuge ich aber, dass die Queue-Logik im Code und in Git liegt. Mit den richtigen Tools könnte ich meine Meinung vielleicht ändern
    [0]: https://www.dbos.dev/
    [1]: https://github.com/NikolayS/pgque

    • Vielleicht ist die Arbeitsweise schwieriger oder einfach nur anders, aber es scheint an Dokumentation, gut durchsuchbaren Artikeln, Erfahrung und Tools zu fehlen
      Ich frage mich, wie Versionierung, Debugging, Tests und Releases funktionieren. Für Datenlokalität und einen einfacheren Stack alles an einem Ort zu haben, klingt gut, aber es fühlt sich so an, als würde dabei viel nützliches Wissen darüber verloren gehen, wie man es „richtig“ macht
    • Ich stimme „Ich möchte die Queue-Logik im Code haben“ zu. Die Aktionen, die auf Daten ausgeführt werden sollen, ändern sich viel häufiger als die Daten selbst, und dass man bei jeder Änderung der Aktion eine Migration machen muss, ergibt für mich keinen Sinn
      Genau deshalb habe ich es auch wirklich gehasst, dass man bei Supabase schon für leicht komplexere Dinge Postgres-Funktionen schreiben musste. In einem früheren Startup haben wir allerdings selbst eine einfache Job-Queue auf Postgres gebaut, und etwas wie pgQue hätte das vermutlich deutlich ausgereifter gemacht
    • Ich bin seit Postgres 7 ein Fan und habe experimentell versucht, so viel wie möglich in PostgreSQL unterzubringen, aber zumindest Developer Experience und Observability fehlen noch immer
      Auch Multi-Master-Erweiterungen sind nicht etwas, das man sofort einsetzen kann und das völlig sicher wäre, daher bin ich vorsichtig, schreibintensive komplexe Aufgaben hineinzupacken, die den Bedarf an Datenbankerweiterungen früher auslösen
    • In Projekten mit DB-Triggern haben wir auch SQL-Code in Git verwaltet
      Beim lokalen Setup haben wir Trigger teils in Django-Migrationen hineingedrückt, damit sie in der lokalen Datenbank landeten
  • Das riecht nach Stored Procedures. Unit-Tests und Versionsverwaltung sind schwierig, und Business-Logik versteckt sich in der Datenbank als „verstecktes Gehirn“
    Außerdem ist es schwer, laute Workloads zu isolieren, es fehlt an Observability, und der gesamte Skalierungsdruck landet bei Postgres. Besonders bei Ein-/Ausgabe wie API-Aufrufen gibt es Defizite. Für Aufgaben, die nur innerhalb der lokalen Datenbank laufen, ist es okay, aber der Einsatzzweck wirkt eng

    • Auch Stored Procedures sind hervorragend, wenn man sie richtig verwendet. Versionsverwaltung geht, indem man ans Namensende eine monoton steigende ID anhängt; wenn es breaking changes braucht, erhöht man die ID, und die alte Version lässt man bestehen, bis sie nicht mehr verwendet wird
      Natürlich braucht man dafür ein ordentliches Datenbank-Upgrade-Verfahren. Wenn Teammitglieder als root beliebige SQL-Migrationen ausführen, bekommt man Probleme
      Unit-Tests sind genauso möglich wie bei anderen SQL-Tests, man muss nur eine Datenbank hochfahren. Wenn man Stored Procedures nicht testen kann, bedeutet das, dass es generell keinen Weg gibt, SQL zu testen, und das ist das eigentliche Problem
      Die Alternative zu Stored Procedures ist nicht, gar keine Business-Logik in die Datenbank zu legen; stattdessen endet SQL oft über die ganze Codebasis verstreut, ist schwer zu testen, schlecht versioniert und gekapselt und unnötig langsam
      Beim Thema Observability ist etwas Wahres dran: SQL-Probleme zu untersuchen ist meist mühsamer als in den meisten Programmiersprachen. Aber wenn Stored Procedures Ein-/Ausgabe- und Skalierungsprobleme verursachen, werden sie falsch eingesetzt; richtig eingesetzt reduzieren sie Ein-/Ausgabe oft massiv und verbessern dadurch die Skalierbarkeit
  • Wenn ich es richtig verstehe, scheint Absurd, gemacht von den Entwicklern des Pi LLM Harness, darauf ausgelegt zu sein, reine Datenbankzugriffe so weit wie möglich zu reduzieren. Ich schaue mir das Thema allerdings gerade erst an
    https://github.com/earendil-works/absurd

    • Kleine Korrektur: absurd scheint eher earendils ursprüngliches Projekt zu sein, das schon vor Mario Zechners Einstieg bei earendil begonnen wurde, und ich habe ihn auch nicht in den Commits gesehen
      Natürlich kenne ich nicht alle Details, daher frage ich aus ehrlichem Interesse
  • Unter „wann man es nicht verwenden sollte“ steht „wenn der Workflow größtenteils außerhalb von Postgres liegt und sich über mehrere heterogene Systeme erstreckt“; dann verstehe ich nicht, wie sich dieses Projekt mit etwas wie Temporal vergleichen lässt
    Ich frage mich, ob ich die durch diese Empfehlung angedeutete Einschränkung falsch verstehe

    • Stimme zu. Wenn man sich das Beispiel https://github.com/microsoft/pg_durable/blob/main/examples/i... ansieht, wird der Wert des Projekts nicht wirklich klar
      Technisch mag es eine interessante Leistung sein, aber so ein SQL zu lesen ist ziemlich bizarr
      SELECT df.start(
      @> (
      ($$SELECT ... FROM demo.invoices WHERE status = 'pending'$$ |=> 'inv')
      ~> df.if_rows('inv',
      $$UPDATE ... SET status = 'processing'$$
      ~> (df.http(...) |=> 'resp')
      ~> df.if($$SELECT $r.ok$$,
      -- klassifizieren, verzweigen, auf Signal warten ...
      ),
      df.sleep(5)
      )
      ),
      'invoice-approval-pipeline'
      );
  • Wir sind im Unternehmen an Azure gebunden und warten weiter darauf, dass Azure PostgreSQL bei modernen Features aufholt.
    Zum Beispiel kann man das hier nicht nutzen: https://www.paradedb.com/blog/hybrid-search-in-postgresql-th...
    Unterstützung für ultrabreite hochdimensionale Vektoren gibt es ebenfalls nicht. Es ist gut, dass pg_durable als Open Source veröffentlicht wird, aber vielleicht sollte man erst einmal die Basisfunktionen einführen, die man bei AWS selbstverständlich bekommt.

    • ParadeDB steht unter AGPL und lässt sich deshalb für große Cloud-Anbieter normalerweise nicht einfach allgemein bereitstellen. In Azure HorizonDB kann man allerdings https://github.com/timescale/pg_textsearch nutzen, und es ist gut möglich, dass es bald auch in Flex verfügbar sein wird.
      Offen gesagt: Ich bin Maintainer von pg_textsearch und inzwischen bei Azure. Die Bemerkung zur Vektorunterstützung habe ich nicht ganz verstanden; gemeint ist also etwas über das von Azure angebotene pgvector + diskann hinaus?
    • Für die Vektorsuche wäre Azure Cosmos DB vielleicht eher passend.
    • Das habt ihr vermutlich schon geprüft, aber mich würde interessieren, warum ihr nicht einfach eine Bare-Metal-VM erstellt und das neueste Postgres installiert.
    • Ich bin PM im Azure-PG-Team und verantworte die AI-Features von Postgres. Die angefragten Funktionen sind tatsächlich verfügbar, und in den letzten 3–6 Monaten gab es viele Fortschritte.
      Bei der hybriden Suche (BM25 + Vektor) ist auch ParadeDBs pg_search kein AWS-natives Feature, sondern muss direkt auf EC2 selbst gehostet werden. Für Azure PostgreSQL haben wir pg_textsearch nativ entwickelt, das dasselbe BM25-Ranking-Modell bietet, und der Hauptbeitragende ist jetzt im Azure-Postgres-Team.
      Dokumentation: https://learn.microsoft.com/en-us/azure/horizondb/ai/full-te...
      Bei hochdimensionalen Vektoren sind wir eher voraus. pgvector mit HNSW hat eine Begrenzung von 2.000 Dimensionen, aber Azure unterstützt pgvector für Vektorspeicherung und -suche und bietet für hochdimensionale, großskalige Workloads pg_diskann, Microsofts graphbasierten Vektorindex. Er unterstützt bis zu 16.000 Dimensionen und bietet außerdem fortgeschrittenes In-Index-Filtering, das WHERE-Bedingungen während der Graph-Traversierung auswertet, sodass bei selektiven Bedingungen kein Recall verloren geht.
      pgvector: https://learn.microsoft.com/en-us/azure/horizondb/ai/vector-...
      DiskANN-Unterstützung für hohe Dimensionen: https://learn.microsoft.com/en-us/azure/horizondb/ai/vector-...
      Diese Funktionen sind derzeit in Azure PostgreSQL verfügbar, insbesondere in Azure HorizonDB Preview. Wenn es einen konkreten Workload gibt, kann man sich das gern genauer ansehen.
  • Das wirkt auf mich wie eine falsche Lösung für ein altes Problem, das DAG-Scheduler wie Apache Airflow schon vor langer Zeit gelöst haben.
    Ich finde es seltsam, den Kontrollfluss in der Datenbank statt im Code speichern zu wollen. Das soll das Projekt nicht herabsetzen, ich verstehe es nur noch nicht ganz.

    • Bei Microsoft gibt es für solche Zwecke das Durable Task Framework[1], das wie Temporal auch als selbst gehosteter unabhängiger Dienst laufen kann und über Azure Functions auch serverlos betrieben werden kann. Wenn ich mich richtig erinnere, ist es sogar älter als Airflow und Temporal.
      Dieses Projekt scheint eher ein datenbankspezifischer Anwendungsfall zu sein. Der Vorteil dürfte sein, dass man den exakten Zustand der Aufgaben direkt in der Datenbank selbst nachverfolgen kann, statt Workflow-Logs mit der Codebasis Zeile für Zeile abgleichen zu müssen. Last und Latenz dürften auch geringer sein, und operativ müsste man eine Komponente weniger betreiben.
      [1] https://learn.microsoft.com/en-us/azure/durable-task/common/...
    • Externe Tools wie Airflow kennen die Datenbanklast nicht. Wenn ein Entwickler 200 gleichzeitige Worker auf die Datenbank loslässt, kann das andere Workloads beeinträchtigen.
      Umgekehrt scheint dieser Ansatz zwar derzeit noch nicht so zu funktionieren, hätte aber potenziell die Möglichkeit, sich anhand von nahezu Echtzeit-Performance-Feedback ohne Roundtrip-Latenzkosten selbst zu regulieren.
  • Auch nach dem Lesen der Dokumentation und Beispiele sind mir einige Dinge nicht klar. Ich frage mich, wie df.wait_for_schedule() funktioniert.
    Wenn man es in der Anwendung aufruft: Ist es idempotent? Wenn man es zweimal mit denselben Parametern ausführt, wird der Tick dann zweimal ausgelöst? Oder ruft man es nur einmal manuell in der Query-Konsole auf? Oder als Teil eines Migrationsskripts?
    Ich frage mich auch, ob timed_out im Beispiel[0] eine feste Konstante ist, die bei einem Timeout zurückgegeben wird. Auch wie Fehler- oder Exception-Handling funktioniert, ist nicht direkt ersichtlich.
    [0] https://github.com/microsoft/pg_durable/blob/main/examples/i...

    • Wenn man df.start() aufruft, erstellt man eine durable function und startet ihre Ausführung gleichzeitig. Dieser Aufruf gibt eine Instanz-ID zurück, die diese Ausführung repräsentiert und mit der man später auf sie verweisen kann.
      Innerhalb dieser durable function wird df.wait_for_signal() aufgerufen; dieser Aufruf wird innerhalb dieser Funktionsinstanz genau einmal ausgeführt, daher sind Duplikate nicht möglich. Wenn der Aufruf von df.start() selbst wegen eines Timeouts erneut ausgeführt wird, kann es zu Duplikaten kommen, aber in diesem Fall wird eine andere Funktionsinstanz erstellt.
      Tritt während der SQL-Ausführung ein unbehandelter Fehler auf, schlägt die Funktionsinstanz fehl, und im Status erscheint der exakt aufgetretene Fehler unverändert.
  • Kannst du erklären, warum man das statt eines Orchestrierungstools außerhalb der Datenbank verwenden sollte? Auch nach dem Lesen von README und Beispielen verstehe ich es noch nicht ganz.

    • Wenn man Snapshot-PITR der Datenbank nutzt, werden auch alle dauerhaften Aufgaben bis zu diesem Zeitpunkt gemeinsam wiederhergestellt.
      Man muss Backups nicht mit anderen Komponenten synchronisieren, die zum selben Datenspeicher gehören; das ist gut für ETL-Pipelines oder zustandsmaschinenartige Aufgaben. Wenn das ETL größtenteils aus SQL besteht, hilft es auch, dass die eigentliche Arbeit auf demselben Server ausgeführt wird.
    • Aus Sicht eines Mitwirkenden lassen sich die Postgres-Kunden von Microsoft ziemlich gleichmäßig in zwei Gruppen teilen: diejenigen, die möglichst viel innerhalb der Datenbank machen wollen, und diejenigen, die Anwendung und Rechenoperationen außerhalb der Datenbank halten möchten.
    • Wenn die Datenbank in der Architektur die einzige zustandsbehaftete Komponente ist, ist das manchmal praktisch.
      Wenn der gesamte Zustand in einer einzigen Datenbank liegt, ist die Wahrscheinlichkeit höher, konsistente Backups zu erhalten.
    • Anwendungs-Workflows lassen sich gut integrieren. Zum Beispiel kann man den Fortschritt über Permalinks in einer Frontend-App anzeigen, Workflows erstellen, die nach einem App-Neustart weiterlaufen, und man muss keine weitere Infrastrukturkomponente hinzufügen.
      Bei https://transport.data.gouv.fr wird Postgres dafür genutzt, und das ist hilfreich in einer Elixir-App, die ziemlich viel Verarbeitung übernimmt. Über pg_durable weiß ich noch nicht viel, aber ich kann das gut nachvollziehen, weil ich ähnliche Lösungen genutzt oder selbst umgesetzt habe.
  • Ist die Datenbank nicht ohnehin schon eine der am schwierigsten zu skalierenden Infrastrukturkomponenten? Ich verstehe nicht, warum man dort auch noch lang laufende Aufgaben unterbringen möchte.

    • Lang laufende Aufgaben in Postgres auszuführen, ist überhaupt nichts Neues. Ein Beispiel ist pg_cron.
      Letztlich sind solche Workloads Aufgaben, die gegen die Datenbank ausgeführt werden, unabhängig davon, ob sie von einer externen Komponente getriggert werden oder nicht. In Daten- oder AI-Pipelines ist es auch üblicher geworden, HTTP-Requests aus der Datenbank heraus zu senden, um zusätzliche Roundtrips und Fehlerquellen durch weitere Komponenten zu vermeiden. Ob man allerdings die Berechnung zu den Daten bringt oder die Daten zur Berechnung, ist eine große Architekturentscheidung, über die viel diskutiert wird.
  • Es wirkt wie ein weiteres https://en.wikipedia.org/wiki/Inner-platform_effect, das nicht nötig wäre, wenn populäre Programmiersprachen oder virtuelle Maschinen bereits Determinismus, messbare und kontrollierbare schrittweise Ausführung, das Anhalten des Laufzeitzustands sowie Serialisierung/Deserialisierung und Wiederaufnahme unterstützen würden.