pg_durable - Funciones SQL durables para PostgreSQL
(github.com/microsoft)- Extensión de funciones durables que maneja reintentos, programación, fan-out en paralelo y ramificación condicional dentro de PostgreSQL usando solo un pequeño DSL de SQL
- Funciona únicamente con Postgres y workers en segundo plano, sin contenedores ni servicios externos
- Todos los pasos registran checkpoints de estado en PostgreSQL, por lo que se reanuda desde el punto de interrupción incluso ante fallas, reinicios o desconexiones
- El motor de orquestación se encarga de la gestión de colas, seguimiento de estado, recuperación ante fallas, coordinación de pasos y reintentos, así que solo hay que escribir SQL
- Reemplaza tareas que requerirían más de 300 líneas de código repetitivo en una implementación manual con una sola llamada al DSL, y está disponible de inmediato como open source en PostgreSQL 17
Descripción general y valor principal
- Función durable resistente a fallas integrada en Postgres que orquesta reintentos, programación, fan-out en paralelo y ramificación condicional con un pequeño DSL de SQL
- Funciona solo con Postgres + workers en segundo plano, sin infraestructura adicional ni necesidad de contenedores o servicios externos
- Actúa como un motor de orquestación que se encarga de la gestión de colas, seguimiento de estado, recuperación ante fallas, coordinación de pasos y reintentos; el usuario solo escribe SQL
Cómo sería implementarlo sin pg_durable
- Para ejecutar 3 agregaciones en paralelo, actualizar un dashboard e incluir reintentos y recuperación ante fallas, se necesitarían más de 300 líneas de código repetitivo
- Elementos que habría que construir manualmente: configuración de colas, gestión y polling de workers, procesamiento de mensajes y seguimiento de estado, manejo de errores y reintentos, y coordinación manual de pasos
- El código de ejemplo incluye múltiples tablas de estado como
job_queue,job_results,job_state,workflow_steps,step_variables,scheduled_jobs, además de un worker de polling, avance del workflow, recuperación ante fallas, coordinador de ejecución paralela, paso de variables, programación y funciones de limpieza - El cálculo de
next_runpara la programación también requiere una librería externa para parsear cron
Cómo se implementa con pg_durable
- La misma agregación paralela + actualización de dashboard se expresa con una sola llamada a
df.start(), usando el operador¶ fan-out y~>para hacer join- Ejemplo: 3 consultas se ramifican en paralelo y luego convergen en el paso
refresh dashboardpara generar el resultado - En el ejemplo de ejecución en vivo, tras 3 pasos ejecutados en paralelo y luego el join, se completa de forma durable hasta
dashboard readyen solo 1.9 segundos
- Ejemplo: 3 consultas se ramifican en paralelo y luego convergen en el paso
- pg_durable se encarga de la gestión de colas, seguimiento de estado, recuperación ante fallas, coordinación de pasos y reintentos
Características principales
-
Durable por defecto
- Cada paso registra checkpoints de estado en PostgreSQL, por lo que el workflow sobrevive a fallas, reinicios y desconexiones
- Se reanuda exactamente desde el punto donde se interrumpió
-
Reintentos automáticos
- Incluye lógica de reintentos integrada para trabajos inestables; si un paso falla, solo se reintenta ese paso y el resto del workflow sigue avanzando
- No hace falta código manual para manejo de errores
-
Observabilidad total en SQL
- Todo el estado del workflow se guarda en tablas de Postgres, por lo que se puede consultar el historial de ejecución, revisar la salida de cada paso y depurar fallas con SQL estándar
- No se necesitan dashboards externos
-
Ejecución en paralelo
- Con el operador
&odf.join()se hace fan-out de tareas independientes y se ejecutan simultáneamente agregaciones, llamadas a API o pasos ETL con coordinación automática
- Con el operador
Patrones que se pueden construir
-
Pipelines ETL
- Encadena cleanup → transform → load con garantía de secuencia; cada paso espera al anterior y, si falla, el pipeline se detiene limpiamente (
~> sequence,|=> variables)
- Encadena cleanup → transform → load con garantía de secuencia; cada paso espera al anterior y, si falla, el pipeline se detiene limpiamente (
-
Agregación en paralelo
- Ejecuta al mismo tiempo el conteo de usuarios, la suma de ingresos y la verificación de inventario, haciendo fan-out con varias consultas y esperando a que todo termine (
&,df.join())
- Ejecuta al mismo tiempo el conteo de usuarios, la suma de ingresos y la verificación de inventario, haciendo fan-out con varias consultas y esperando a que todo termine (
-
Procesamiento de pedidos
- Captura un ID de pedido y lo pasa por validación, procesamiento y finalización, con flujo automático de variables entre pasos (
|=> capture,$var substitution,df.sleep())
- Captura un ID de pedido y lo pasa por validación, procesamiento y finalización, con flujo automático de variables entre pasos (
-
Jobs programados
- Hace polling de APIs, archivado de registros y sincronización de datos con un cron schedule; el loop corre permanentemente y sobrevive a reinicios (
@> loop,df.wait_for_schedule())
- Hace polling de APIs, archivado de registros y sincronización de datos con un cron schedule; el loop corre permanentemente y sobrevive a reinicios (
-
Ramificación condicional
- Revisa trabajos en espera, número de filas o flags para decidir entre procesar o saltar, con la lógica condicional en SQL y no en la aplicación (
df.if(),?> conditional)
- Revisa trabajos en espera, número de filas o flags para decidir entre procesar o saltar, con la lógica condicional en SQL y no en la aplicación (
-
Validación de múltiples pasos
- Obtiene datos → valida esquema → revisa reglas de negocio → aprueba/rechaza; cada paso queda checkpointed para no perder el progreso aunque haya fallas
-
Mantenimiento de base de datos
- Detecta bloqueos para autovacuum, bloat de tablas y riesgo de wraparound, los expone para revisión y luego los corrige de forma durable incluso tras reinicios (
?> conditional,df.wait_for_signal(),@> loop)
- Detecta bloqueos para autovacuum, bloat de tablas y riesgo de wraparound, los expone para revisión y luego los corrige de forma durable incluso tras reinicios (
-
Azure Functions y HTTP
- Con
df.http()llama directamente desde SQL a Azure Functions o endpoints HTTPS permitidos, y procesa en línea chunking de documentos, enriquecimiento de filas o clasificación de registros
- Con
-
Aprobación con humano en el ciclo
- Aprueba automáticamente tareas rutinarias y pausa tareas de alto riesgo (facturas grandes, operaciones destructivas) hasta recibir una señal de aprobación humana (
df.wait_for_signal(),df.if())
- Aprueba automáticamente tareas rutinarias y pausa tareas de alto riesgo (facturas grandes, operaciones destructivas) hasta recibir una señal de aprobación humana (
Soporte de escritura con IA
- Si se describe el workflow en inglés natural, Copilot genera el SQL correcto de durable functions, sin necesidad de aprender la sintaxis
- El repositorio incluye la skill reutilizable
pg-durable-sql, que enseña a GitHub Copilot y otros agentes a generar correctamente SQL con operadores, sustitución de variables, loops, joins paralelos y más
Disponible como open source
- Se ofrece como open source completo sin lista de espera ni lock-in; se puede clonar el repositorio, compilarlo y ejecutarlo de inmediato en tu propio PostgreSQL
- Permite aplicar orquestación durable en laptops, servidores o la nube
Opción administrada con Azure HorizonDB
- Azure HorizonDB es el nuevo servicio cloud de PostgreSQL de Microsoft, con pg_durable integrado, lo que permite conservar las durable functions escritas y al mismo tiempo sumar escalabilidad empresarial, seguridad e IA
- Hasta 3× más rendimiento, autoescalado de almacenamiento hasta 128 TB y escalado horizontal de cómputo hasta 3,072 vCores
- Detección de amenazas en tiempo real con Microsoft Defender y gestión de identidad con Microsoft Entra ID
- Búsqueda vectorial con Filtered DiskANN, ranking semántico y curación de modelos de IA dentro de la base de datos
- Mirroring casi en tiempo real con Microsoft Fabric, integración con VS Code y conexión con GitHub Copilot
-
Pipeline de IA integrado
- HorizonDB superpone un pipeline de IA administrado de extremo a extremo sobre la ejecución durable de pg_durable, con checkpoints, reintentos y tolerancia a fallas en cada paso
- Flujo de etapas: Ingest (carga de documentos y datos) → Chunk (división de contenido) → Embed (vectorización) → Index (almacenamiento en DiskANN) → Serve (búsqueda y ranking)
1 comentarios
Comentarios en Hacker News
2026 parece que será el año de las colas en Postgres: están tendencias como DBOS[0] y pgQue[1], y está bueno que la comunidad esté creando este tipo de opciones
Aun así, desde la perspectiva de alguien que antes fue ingeniero de aplicaciones, prefiero que la lógica de la cola esté en el código y en Git. Con la herramienta adecuada quizá cambie de opinión
[0]: https://www.dbos.dev/
[1]: https://github.com/NikolayS/pgque
Me pregunto cómo se manejan el control de versiones, el debugging, las pruebas y los releases. Tener todo en un solo lugar por localidad de datos y por simplicidad del stack suena bien, pero da la sensación de que se pierde mucho conocimiento útil sobre cómo hacerlo “bien”
Por eso también me molestó mucho que en Supabase, apenas querías hacer algo un poco complejo, ya tenías que crear una función de Postgres. Aun así, en una startup anterior hicimos nuestra propia cola de trabajos simple sobre Postgres, y si hubiera existido algo como pgQue, probablemente habría quedado mucho más pulido
Como incluso las extensiones multi-master no son algo que se pueda usar de inmediato con total seguridad, me da cautela meter trabajos complejos con muchas escrituras que adelanten la necesidad de escalar la base de datos
En algunos casos hasta lo empujábamos dentro de migraciones de Django para que, al configurar en local, los triggers quedaran instalados en la base de datos local
Esto huele a procedimientos almacenados. Son difíciles de probar unitariamente y de versionar, y la lógica de negocio queda escondida dentro de la base de datos como una “mente oculta”
También es difícil aislar cargas de trabajo ruidosas, no hay observabilidad y toda la presión de escalado recae sobre Postgres. Sobre todo, falta entrada/salida para cosas como llamadas a APIs. Para trabajos que solo corren dentro de la base de datos local está bien, pero el caso de uso parece limitado
Claro, para eso hace falta un procedimiento de upgrade de base de datos bien hecho. Si un compañero ejecuta migraciones SQL arbitrarias como root, la vas a pasar mal
Las pruebas unitarias también se pueden hacer igual que cualquier otra prueba de SQL; solo hace falta levantar la base de datos. Si no puedes probar procedimientos almacenados, entonces tampoco tienes forma de probar SQL en general, y ese sí es el verdadero problema
La alternativa a los procedimientos almacenados no es no poner nada de lógica de negocio en la base de datos, sino que muchas veces terminas con SQL disperso por todo el codebase, difícil de probar, con mal versionado y encapsulación, y además innecesariamente lento
Lo de la observabilidad es cierto en parte: inspeccionar problemas de SQL suele requerir más trabajo manual que en la mayoría de los lenguajes de programación. Pero si los procedimientos almacenados te están causando problemas de entrada/salida y de escalado, entonces se están usando mal; bien usados, muchas veces reducen muchísimo la entrada/salida y mejoran la escalabilidad
Si lo entendí bien, Absurd, hecho por los desarrolladores del harness Pi LLM, parece ir en la dirección de reducir al máximo el acceso puro a base de datos. Apenas estoy empezando a ver este tema
https://github.com/earendil-works/absurd
Igual no conozco todos los detalles, así que tengo curiosidad genuina
En “cuándo no deberías usarlo” dice “cuando el workflow está mayormente fuera de Postgres y se extiende por varios sistemas heterogéneos”, pero entonces no entiendo cómo este proyecto podría compararse con algo como Temporal
Me pregunto si estoy entendiendo mal la limitación que implica esa recomendación
Técnicamente puede ser un logro interesante, pero leer SQL así resulta bastante extraño
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');En la empresa estamos atados a Azure y seguimos esperando a que Azure PostgreSQL se ponga al día con las funciones modernas.
Por ejemplo, no se puede usar esto: https://www.paradedb.com/blog/hybrid-search-in-postgresql-th...
Tampoco soporta vectores de ultra gran ancho y alta dimensionalidad. Está bien que publiquen pg_durable como open source, pero ojalá primero incorporaran las funciones básicas que en AWS se dan por sentadas.
Para transparentarlo, soy mantenedor de pg_textsearch y ahora estoy en Azure. No entendí con precisión lo que comentas sobre soporte vectorial; me pregunto si buscas algo más allá de pgvector + diskann, que ya se ofrece en Azure.
En búsqueda híbrida (BM25 + vectores), el pg_search de ParadeDB tampoco es una función nativa de AWS; hay que alojarlo directamente en EC2. En Azure PostgreSQL hicimos nativo pg_textsearch, que ofrece el mismo modelo de ranking BM25, y uno de sus principales contribuidores ahora está en el equipo de Azure Postgres.
Documentación: https://learn.microsoft.com/en-us/azure/horizondb/ai/full-te...
De hecho, en vectores de alta dimensionalidad vamos más adelantados. pgvector con HNSW tiene un límite de 2,000 dimensiones, pero Azure soporta pgvector para almacenamiento y búsqueda vectorial, y para cargas de trabajo grandes y de alta dimensionalidad ofrece pg_diskann, el índice vectorial basado en grafos de Microsoft. Soporta hasta 16,000 dimensiones y también ofrece filtrado avanzado dentro del índice, evaluando condiciones
WHEREdurante el recorrido del grafo para no perder recall en condiciones selectivas.pgvector: https://learn.microsoft.com/en-us/azure/horizondb/ai/vector-...
Soporte de alta dimensionalidad en DiskANN: https://learn.microsoft.com/en-us/azure/horizondb/ai/vector-...
Estas funciones ya están disponibles en Azure PostgreSQL, especialmente en Azure HorizonDB Preview. Si tienes una carga de trabajo específica, se puede revisar con más detalle.
Esto se siente como una mala solución para un problema viejo que los programadores de DAG como Apache Airflow llevan mucho tiempo resolviendo.
Se me hace raro querer guardar el flujo de control en la base de datos y no en código. No busco demeritar el proyecto, solo que todavía no lo termino de entender.
Este proyecto parece apuntar a casos de uso más específicos de base de datos. La ventaja probablemente sea que puedes seguir el estado exacto del trabajo directamente dentro de la base de datos, sin tener que ir cotejando logs del workflow con el codebase línea por línea. También parecería tener menor carga y menor latencia, además de reducir en uno los componentes que hay que operar.
[1] https://learn.microsoft.com/en-us/azure/durable-task/common/...
En cambio, aunque no parece funcionar así por ahora, este enfoque podría ajustarse por sí mismo con retroalimentación de rendimiento casi en tiempo real, sin el costo de latencia de ida y vuelta.
Incluso leyendo la documentación y los ejemplos, hay varias cosas que no me quedan claras. Me pregunto cómo funciona
df.wait_for_schedule()Si se llama desde la aplicación, si es idempotente, si al ejecutarlo dos veces con los mismos parámetros se dispara el tick dos veces, si es algo que se llama manualmente una sola vez desde la consola de consultas, o si se ejecuta como parte de un script de migración, no me queda claro
También me pregunto si
timed_outen el ejemplo[0] es una constante fija que se devuelve cuando se agota el tiempo. Tampoco se ve de inmediato cómo se manejan los errores o las excepciones[0] https://github.com/microsoft/pg_durable/blob/main/examples/i...
df.start(), se crea una función durable y al mismo tiempo se inicia su ejecución. Esa llamada devuelve un ID de instancia que representa esa ejecución y que luego puede usarse para referirse a ellaDentro de esa función durable se llama a
df.wait_for_signal(), y esa llamada se ejecuta exactamente una vez dentro de esa instancia de función, así que no puede duplicarse. La llamada adf.start()en sí podría duplicarse si se agota el tiempo y se vuelve a ejecutar, pero en ese caso se crearía una instancia de función distintaSi durante la ejecución de SQL ocurre un error no controlado, la instancia de función falla y en el estado queda registrado exactamente el error que ocurrió
¿Podrías explicar por qué habría que usar esto en lugar de una herramienta de orquestación fuera de la base de datos? Incluso leyendo el README y los ejemplos todavía no lo termino de entender
Como no hace falta sincronizar los respaldos con otros componentes que pertenezcan al mismo almacenamiento de datos, viene bien para pipelines de ETL o trabajos tipo máquina de estados. Si el ETL es mayormente SQL, también ayuda que el trabajo real se ejecute en el mismo servidor
Si todo el estado está en una sola base de datos, también es más probable obtener respaldos consistentes
En https://transport.data.gouv.fr usan Postgres para ese tipo de cosas, y les ha servido en una app de Elixir que hace bastante procesamiento. Todavía no conozco bien pg_durable, pero me identifico porque ya he usado o implementado soluciones parecidas
¿La base de datos no es ya una de las piezas de infraestructura más difíciles de escalar? No entiendo por qué querrías además cargarle trabajos de larga duración
Al final, estas cargas de trabajo serán tareas que se ejecutan contra la base de datos, ya sea que las dispare o no un componente externo. En pipelines de datos o de IA también se ha vuelto más común enviar consultas HTTP desde la base de datos para evitar viajes de ida y vuelta y puntos de falla adicionales por componentes extra. Aun así, decidir si llevar el cómputo hacia los datos o los datos hacia el cómputo es una elección de diseño importante y muy debatida
Se siente como otra https://en.wikipedia.org/wiki/Inner-platform_effect que no haría falta si los lenguajes de programación o máquinas virtuales populares ya soportaran determinismo, ejecución paso a paso medible y controlable, pausa del estado en tiempo de ejecución, serialización y deserialización, y reanudación