3 points par GN⁺ 4 시간 전 | 1 commentaires | Partager sur WhatsApp
  • Extension de durable functions pour orchestrer reprises, planification, fan-out parallèle et branchement conditionnel directement dans PostgreSQL avec une petite DSL SQL
  • Fonctionne uniquement avec Postgres et des background workers, sans conteneurs ni services externes
  • Chaque étape enregistre son état comme point de contrôle dans PostgreSQL, ce qui permet de reprendre au point d’arrêt après un crash, un redémarrage ou une coupure de connexion
  • Plus besoin d’implémenter soi-même la gestion de file, le suivi d’état, la reprise après crash, la coordination des étapes ou les retries : il suffit d’écrire du SQL et le moteur d’orchestration s’en charge
  • Remplace des tâches qui exigeraient plus de 300 lignes de boilerplate par un seul appel DSL, disponible immédiatement en open source sur PostgreSQL 17

Vue d’ensemble et proposition de valeur

  • Durable function intégrée à Postgres et résistante aux crashs (crash-proof), qui orchestre retries, planification, fan-out parallèle et branchement conditionnel avec une petite DSL SQL
  • Fonctionne uniquement avec Postgres + background workers, sans infrastructure supplémentaire, conteneur séparé ni service externe
  • Joue le rôle de moteur d’orchestration en prenant en charge la gestion des files, le suivi d’état, la reprise après crash, la coordination des étapes et les retries ; l’utilisateur n’écrit que du SQL

Implémentation sans pg_durable

  • Pour exécuter 3 agrégations en parallèle, puis rafraîchir un dashboard avec retries et reprise après crash, il faut plus de 300 lignes de boilerplate
    • Éléments à construire soi-même : configuration et mise en place de la file, gestion des workers et polling, traitement des messages et suivi d’état, gestion des erreurs et retries, coordination manuelle des étapes
    • Le code d’exemple inclut de nombreuses tables d’état comme job_queue, job_results, job_state, workflow_steps, step_variables, scheduled_jobs, ainsi qu’un worker de polling, la progression du workflow, la reprise après crash, un coordinateur d’exécution parallèle, la transmission de variables, la planification et des fonctions de nettoyage
    • Le calcul de next_run pour la planification nécessite en plus une bibliothèque externe de parsing cron

Implémentation avec pg_durable

  • La même agrégation parallèle suivie d’un rafraîchissement de dashboard s’exprime en un seul appel df.start(), avec fan-out via l’opérateur & puis join avec ~>
    • Exemple : 3 requêtes partent en parallèle puis convergent vers l’étape refresh dashboard pour produire le résultat
    • Dans l’exemple d’exécution en direct, les 3 étapes s’exécutent en parallèle puis sont jointes ; le dashboard est prêt de manière durable en 1,9 seconde
  • Gestion des files, suivi d’état, reprise après crash, coordination des étapes et retries sont entièrement pris en charge par pg_durable

Principales caractéristiques

  • Durable par défaut

    • Chaque étape enregistre son état comme point de contrôle dans PostgreSQL, ce qui permet au workflow de survivre à un crash, un redémarrage ou une coupure de connexion
    • Reprise exactement au point d’interruption
  • Retries automatiques

    • Logique de retry intégrée pour les tâches instables ; en cas d’échec d’une étape, seule cette étape est relancée et le reste du workflow continue
    • Plus besoin de code manuel de gestion des erreurs
  • Observabilité complète en SQL

    • Tous les états du workflow sont stockés dans des tables Postgres, ce qui permet d’interroger l’historique d’exécution, de vérifier la sortie des étapes et de déboguer les échecs en SQL standard
    • Aucun dashboard externe n’est nécessaire
  • Exécution parallèle

    • L’opérateur & ou df.join() permet de faire du fan-out sur des tâches indépendantes, avec exécution simultanée et coordination automatique pour des agrégations, appels API ou étapes ETL

Patterns possibles

  • Pipelines ETL

    • Enchaîner cleanup → transform → load avec garantie d’ordre séquentiel, chaque étape attendant la précédente et stoppant proprement le pipeline en cas d’échec (~> sequence, |=> variables)
  • Agrégation parallèle

    • Calculer simultanément le nombre d’utilisateurs, le total du chiffre d’affaires et l’état du stock, avec fan-out sur plusieurs requêtes puis attente de la fin globale (&, df.join())
  • Traitement de commandes

    • Capturer un ID de commande puis le transmettre aux étapes de validation, traitement et finalisation, avec circulation automatique des variables entre étapes (|=> capture, $var substitution, df.sleep())
  • Jobs planifiés

    • Polling d’API, archivage d’enregistrements et synchronisation de données sur planification cron, avec boucle persistante qui survit aux redémarrages (@> loop, df.wait_for_schedule())
  • Branchement conditionnel

    • Vérifier des tâches en attente, un nombre de lignes ou des flags pour décider de traiter ou d’ignorer, avec une logique de branchement située en SQL et non dans l’application (df.if(), ?> conditional)
  • Validation en plusieurs étapes

    • Récupération des données → validation du schéma → vérification des règles métier → approbation/rejet, chaque étape étant checkpointée pour ne pas perdre l’avancement en cas d’échec
  • Maintenance de base de données

    • Détecter les facteurs bloquant autovacuum, le bloat de tables et les risques de wraparound, puis les exposer pour revue avant correction durable même après redémarrage (?> conditional, df.wait_for_signal(), @> loop)
  • Azure Functions & HTTP

    • Avec df.http(), appeler directement depuis SQL des Azure Functions ou des endpoints HTTPS autorisés, pour faire en ligne du chunking de documents, de l’enrichissement de lignes ou de la classification d’enregistrements
  • Approbation humaine dans la boucle

    • Approuver automatiquement les tâches courantes et mettre en pause les tâches à haut risque (grosses factures, opérations destructrices) jusqu’à réception d’un signal d’approbation humaine (df.wait_for_signal(), df.if())

Assistance à l’écriture par l’IA

  • Il suffit de décrire le workflow en anglais courant pour que Copilot génère le SQL correct de durable function, sans avoir à apprendre la syntaxe
  • Le dépôt inclut la compétence d’agent réutilisable pg-durable-sql, qui apprend à GitHub Copilot et à d’autres agents à générer correctement les opérateurs, substitutions de variables, boucles, joins parallèles, etc.

Disponible en open source

  • Fourni entièrement en open source, sans liste d’attente ni lock-in : il suffit de cloner le dépôt, de le compiler et de l’exécuter immédiatement sur son propre PostgreSQL
  • Permet d’appliquer une orchestration durable sur un notebook, un serveur ou dans le cloud

Option managée Azure HorizonDB

  • Azure HorizonDB est le nouveau service cloud PostgreSQL de Microsoft, avec pg_durable intégré, ce qui permet de conserver telles quelles les durable functions écrites tout en ajoutant montée en charge d’entreprise, sécurité et IA
    • Jusqu’à 3× plus rapide, extension automatique du stockage jusqu’à 128 To, scale-out compute jusqu’à 3 072 vCore
    • Détection des menaces en temps réel avec Microsoft Defender, gestion des identités avec Microsoft Entra ID
    • Recherche vectorielle Filtered DiskANN, classement sémantique, curation de modèles d’IA dans la base
    • Mirroring quasi temps réel avec Microsoft Fabric, intégration VS Code, liaison avec GitHub Copilot
  • Pipeline IA intégré

    • HorizonDB superpose un pipeline IA managé de bout en bout sur l’exécution durable de pg_durable, avec point de contrôle, retry et sécurité face aux crashs à chaque étape
    • Flux d’étapes : Ingest (chargement de documents et données) → Chunk (découpage du contenu) → Embed (vectorisation) → Index (stockage DiskANN) → Serve (recherche et ranking)

1 commentaires

 
GN⁺ 4 시간 전
Commentaires sur Hacker News
  • 2026 semble être l’année des files d’attente Postgres : il y a une dynamique avec DBOS[0], pgQue[1], et c’est chouette que la communauté crée ce genre d’options
    Cela dit, en tant qu’ancien ingénieur applicatif, je préfère que la logique de queue reste dans le code et dans Git. Avec les bons outils, je pourrais peut-être changer d’avis
    [0]: https://www.dbos.dev/
    [1]: https://github.com/NikolayS/pgque

    • C’est peut-être plus difficile, ou simplement différent comme façon de travailler, mais il semble manquer de documentation, d’articles faciles à retrouver, de retours d’expérience et d’outils
      Je me demande comment on gère le versioning, le débogage, les tests et les releases. Tout mettre au même endroit pour la localité des données et la simplification de la stack paraît séduisant, mais j’ai l’impression qu’on perd beaucoup de savoir utile sur la manière de le faire « correctement »
    • D’accord avec « je veux garder la logique de queue dans le code ». Les actions à effectuer sur les données changent bien plus souvent que les données elles-mêmes, et je ne vois pas l’intérêt de devoir faire une migration à chaque fois qu’on modifie le comportement
      C’est aussi pour ça que j’ai vraiment détesté le fait que, sur Supabase, dès qu’on voulait faire quelque chose d’un peu complexe, il fallait créer des fonctions Postgres. Cela dit, dans une précédente startup, nous avions construit nous-mêmes une file de tâches simple sur Postgres, et s’il y avait eu quelque chose comme pgQue, cela aurait sans doute été bien plus abouti
    • Je suis fan depuis l’époque de Postgres 7 et j’ai essayé, à titre expérimental, de mettre autant de choses que possible dans PostgreSQL, mais au minimum l’expérience développeur et l’observabilité restent insuffisantes
      Même les extensions multi-master ne sont pas vraiment du genre immédiatement utilisables et totalement sûres, donc je reste prudent à l’idée d’y mettre des traitements complexes très orientés écriture, qui accélèrent le besoin d’étendre la base de données
    • Sur les projets avec des triggers DB, nous versions aussi le code SQL dans Git
      Pour la configuration locale, il nous est même arrivé de les injecter dans des migrations Django afin que les triggers soient présents dans la base locale
  • Ça sent les procédures stockées. C’est difficile à tester unitairement, difficile à versionner, et la logique métier se retrouve cachée dans la base de données comme un « cerveau caché »
    C’est aussi difficile d’isoler les workloads bruyants, il n’y a pas d’observabilité, et toute la pression de montée en charge retombe sur Postgres. Surtout, les entrées/sorties comme les appels d’API manquent. Pour des tâches qui tournent uniquement dans la base locale, pourquoi pas, mais l’usage semble limité

    • Les procédures stockées sont excellentes quand elles sont bien utilisées. Pour le versioning, il suffit d’ajouter un ID monotone croissant à la fin du nom, d’incrémenter l’ID lorsqu’un changement cassant est nécessaire, et de garder les anciennes versions jusqu’à ce qu’elles ne soient plus utilisées
      Bien sûr, il faut avoir une vraie procédure de mise à niveau de la base. Si les membres de l’équipe exécutent des migrations SQL arbitraires en root, vous allez souffrir
      Les tests unitaires sont possibles comme pour n’importe quel autre test SQL, il faut simplement lancer une base de données. Si vous ne pouvez pas tester des procédures stockées, cela signifie que vous ne pouvez pas tester le SQL lui-même, et c’est ça le vrai problème
      L’alternative aux procédures stockées, ce n’est pas de ne mettre aucune logique métier dans la base, mais souvent d’avoir du SQL éparpillé partout dans le code, difficile à tester, mal versionné, mal encapsulé, et inutilement lent
      Pour l’observabilité, c’est partiellement vrai : inspecter des problèmes SQL demande généralement plus d’effort qu’avec la plupart des langages de programmation. Mais si des procédures stockées créent des problèmes d’entrées/sorties et de montée en charge, c’est qu’elles sont mal utilisées ; bien employées, elles réduisent souvent fortement les entrées/sorties et améliorent la scalabilité
  • Si j’ai bien compris, Absurd, créé par les développeurs du harnais Pi LLM, semble aller dans le sens d’une réduction maximale des accès directs à la base de données. Je viens seulement de commencer à regarder le sujet
    https://github.com/earendil-works/absurd

    • Petite correction : absurd semble être le projet originel d’earendil, commencé avant que Mario Zechner ne les rejoigne, et je ne l’ai pas vu dans les commits
      Bien sûr, je ne connais pas tous les détails, donc je suis sincèrement curieux
  • Dans la section « quand ne pas l’utiliser », il est écrit « lorsque le workflow est majoritairement hors de Postgres et s’étend sur plusieurs systèmes hétérogènes » ; dans ce cas, je ne vois pas comment ce projet peut être comparé à quelque chose comme Temporal
    Je me demande si je comprends mal les limites implicites de cette recommandation

    • D’accord. En regardant l’exemple https://github.com/microsoft/pg_durable/blob/main/examples/i..., je comprends mal la valeur du projet
      Techniquement, c’est peut-être une réalisation intéressante, mais lire du SQL comme ça est assez étrange
      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$$,
      -- classify, branch, wait for signal ...
      ),
      df.sleep(5)
      )
      ),
      'invoice-approval-pipeline'
      );
  • On est coincés sur Azure au boulot, et on attend toujours qu’Azure PostgreSQL rattrape les fonctionnalités modernes.
    Par exemple, on ne peut pas utiliser ceci : https://www.paradedb.com/blog/hybrid-search-in-postgresql-th...
    Il n’y a pas non plus de prise en charge des vecteurs de très grande dimension. C’est bien de publier pg_durable en open source, mais j’aimerais déjà qu’ils adoptent les fonctionnalités de base qu’on a naturellement sur AWS

    • ParadeDB est sous AGPL, donc c’est généralement difficile à proposer chez les grands fournisseurs cloud. En revanche, sur Azure HorizonDB on peut utiliser https://github.com/timescale/pg_textsearch, et il y a de fortes chances que ça arrive bientôt aussi sur Flex.
      Pour être transparent, je suis mainteneur de pg_textsearch et je suis maintenant chez Azure. Je n’ai pas bien compris la remarque sur la prise en charge des vecteurs : est-ce que vous cherchez quelque chose qui aille au-delà de pgvector + diskann proposés sur Azure ?
    • Pour la recherche vectorielle, Azure Cosmos DB ne serait-il pas plus adapté ?
    • Vous l’avez sans doute déjà envisagé, mais je me demande pourquoi il ne suffirait pas de créer une VM nue et d’y installer la dernière version de Postgres
    • Je suis PM de l’équipe Azure PG en charge des fonctionnalités IA de Postgres. Les fonctionnalités demandées sont bien disponibles, et il y a eu beaucoup de progrès ces 3 à 6 derniers mois.
      Pour la recherche hybride (BM25 + vecteur), le pg_search de ParadeDB n’est pas non plus une fonctionnalité native AWS ; il faut l’héberger soi-même sur EC2. Sur Azure PostgreSQL, nous avons développé nativement pg_textsearch, qui fournit le même modèle de classement BM25, et son principal contributeur fait actuellement partie de l’équipe Azure Postgres.
      Documentation : https://learn.microsoft.com/en-us/azure/horizondb/ai/full-te...
      Pour les vecteurs de grande dimension, c’est même un domaine où nous sommes en avance. pgvector avec HNSW a une limite de 2 000 dimensions, mais Azure prend en charge pgvector pour le stockage et la recherche vectoriels, et pour les charges de travail à grande échelle et en haute dimension, propose pg_diskann, l’index vectoriel orienté graphe de Microsoft. Il prend en charge jusqu’à 16 000 dimensions et fournit aussi un filtrage avancé directement dans l’index, avec évaluation des conditions WHERE pendant la traversée du graphe, ce qui évite de perdre en rappel sur les conditions sélectives.
      pgvector : https://learn.microsoft.com/en-us/azure/horizondb/ai/vector-...
      Prise en charge haute dimension de DiskANN : https://learn.microsoft.com/en-us/azure/horizondb/ai/vector-...
      Ces fonctionnalités sont actuellement disponibles sur Azure PostgreSQL, en particulier dans Azure HorizonDB Preview. Si vous avez une charge de travail précise en tête, on peut l’examiner plus en détail
  • J’ai l’impression que c’est une mauvaise réponse à un vieux problème que des planificateurs de DAG comme Apache Airflow résolvent depuis longtemps.
    Je trouve étrange de vouloir stocker le flux de contrôle dans la base de données plutôt que dans le code. Ce n’est pas pour rabaisser le projet, c’est juste que je ne comprends pas encore bien

    • Microsoft a pour ce type d’usage le framework Durable Task[1], qui peut fonctionner comme service indépendant auto-hébergé, à la manière de Temporal, et aussi en serverless avec Azure Functions. Si je me souviens bien, c’est même antérieur à Airflow et Temporal.
      Ce projet semble davantage viser un cas d’usage spécialisé base de données. L’avantage est sans doute qu’on peut suivre l’état exact du travail directement dans la base elle-même, sans avoir à faire du traçage ligne par ligne en recoupant les logs du workflow avec la base de code. La charge et la latence seraient aussi plus faibles, et cela réduit probablement d’un composant ce qu’il faut faire tourner en production.
      [1] https://learn.microsoft.com/en-us/azure/durable-task/common/...
    • Des outils externes comme Airflow ne peuvent pas connaître la charge de la base de données. Si un développeur lance 200 workers concurrents sur la base, cela peut perturber les autres charges de travail.
      À l’inverse, même si cela ne semble pas être le cas actuellement, cette approche pourrait potentiellement s’autoréguler grâce à un retour de performance quasi temps réel, sans coût de latence lié aux allers-retours
  • Même après avoir lu la documentation et les exemples, certains points restent flous. Je me demande comment fonctionne df.wait_for_schedule()
    Si on l’appelle depuis l’application, est-ce idempotent ? Si on l’exécute deux fois avec les mêmes paramètres, est-ce que le tick se produit deux fois ? Est-ce quelque chose qu’on appelle manuellement une seule fois depuis une console SQL, ou qu’on exécute dans le cadre d’un script de migration ? Je ne le comprends pas bien
    Je me demande aussi si timed_out dans l’exemple[0] est une constante fixe renvoyée en cas de dépassement de temps. La gestion des erreurs ou des exceptions ne saute pas non plus immédiatement aux yeux
    [0] https://github.com/microsoft/pg_durable/blob/main/examples/i...

    • Quand on appelle df.start(), cela crée une fonction durable et démarre son exécution en même temps. Cet appel renvoie un ID d’instance représentant cette exécution, qui peut ensuite servir à la référencer
      À l’intérieur de cette fonction durable, on appelle df.wait_for_signal(), et cet appel n’est exécuté qu’une seule fois exactement dans cette instance de fonction, donc il ne peut pas y avoir de doublon. Si l’appel à df.start() lui-même expire et est relancé, cela peut produire un doublon, mais dans ce cas une autre instance de fonction est créée
      Si une erreur non gérée survient pendant l’exécution SQL, l’instance de fonction échoue et l’état remonte exactement l’erreur qui s’est produite
  • Pourrais-tu expliquer pourquoi utiliser ça plutôt qu’un outil d’orchestration en dehors de la base de données ? Même après avoir lu le README et les exemples, je ne comprends toujours pas bien

    • Avec le PITR par snapshot de la base de données, toutes les tâches durables jusqu’à un instant précis sont restaurées en même temps
      Il n’est pas nécessaire de synchroniser les sauvegardes avec d’autres composants appartenant au même stockage d’état, ce qui est bien pour les pipelines ETL ou les tâches de type machine à états. Si l’ETL est majoritairement en SQL, le fait que le travail réel s’exécute sur le même serveur aide aussi
    • Du point de vue d’un contributeur, les clients Postgres de Microsoft se répartissent assez uniformément en deux groupes : ceux qui veulent faire le plus de choses possible dans la base de données, et ceux qui préfèrent garder l’application et le calcul en dehors de la base
    • C’est parfois pratique quand, dans l’architecture, la base de données est le seul composant avec état
      Si tout l’état se trouve dans une seule base, on a aussi plus de chances d’obtenir des sauvegardes cohérentes
    • Cela peut bien s’intégrer aux workflows applicatifs. Par exemple, on peut afficher la progression depuis un lien permanent dans une app front-end, créer des workflows qui continuent après le redémarrage de l’application, et éviter d’ajouter un composant d’infrastructure supplémentaire
      Sur https://transport.data.gouv.fr, Postgres est utilisé à ce genre de fin, et c’est utile dans une application Elixir qui fait pas mal de traitement. Je ne connais pas encore bien pg_durable, mais j’ai déjà utilisé ou implémenté des solutions similaires, donc ça me parle
  • La base de données n’est-elle pas déjà l’un des composants d’infrastructure les plus difficiles à faire évoluer ? Je ne vois pas pourquoi on voudrait en plus y mettre des tâches de longue durée

    • Exécuter des tâches de longue durée dans Postgres n’a rien de vraiment nouveau. pg_cron en est un exemple
      Au final, ce type de charge correspond à des tâches qui s’exécuteront contre la base de données, qu’elles soient déclenchées ou non par un composant externe. Dans les pipelines de données ou d’IA, il est aussi devenu plus courant d’envoyer des requêtes HTTP depuis la base de données pour éviter des allers-retours et des points de défaillance supplémentaires liés à des composants externes. Cela dit, faire venir le calcul vers les données ou les données vers le calcul reste un choix d’architecture important et très débattu
  • Cela donne l’impression d’un énième https://en.wikipedia.org/wiki/Inner-platform_effect qui n’aurait pas été nécessaire si les langages de programmation ou machines virtuelles populaires prenaient déjà en charge le déterminisme, une exécution pas à pas mesurable et contrôlable, la suspension de l’état d’exécution, ainsi que la sérialisation/désérialisation et la reprise