Cómo hacer que PostgreSQL complete tareas en segundo plano incluso después de un reinicio
Imagina este escenario: estás escribiendo un flujo de trabajo complejo de procesamiento de datos. Necesitas obtener registros, enviarlos a una API externa, actualizar el estado en la base de datos y luego iniciar la generación de informes. Si la base de datos "se cae" o el servidor se reinicia a mitad del proceso, un procedimiento estándar de PL/pgSQL simplemente se abortará. El resultado es datos "colgantes" y un dolor de cabeza: ¿cómo determinas en qué etapa se detuvo todo, y cómo lo reinicias de forma segura?
Por lo general, resolvemos esto con soluciones alternativas. Configuramos pg_cron, creamos tablas con colas de trabajos, escribimos workers en Python o Go que constantemente consultan la base de datos. En el peor de los casos, arrastramos monstruos pesados como Temporal o Airflow al proyecto. Pero los ingenieros de Microsoft decidieron que toda esta complejidad era innecesaria y publicaron la extensión pg_durable.
Por qué los desarrolladores necesitan esto
La idea principal detrás del proyecto es darle a PostgreSQL la capacidad de ejecutar funciones de larga duración que sean resilientes a fallos. En la terminología de los autores, esto se llama "Ejecución Duradera".
Si tu flujo de trabajo está definido a través de pg_durable, la base de datos guardará su estado después de cada paso. Si el servidor se reinicia justo en medio de ejecutar una consulta pesada o una llamada a la API, la extensión retomará la tarea desde el último checkpoint exitoso. Ya no necesitas pegar juntos trabajos cron, workers y tablas de estado.
Cómo funciona internamente
El proyecto está escrito en Rust usando pgrx. Arquitectónicamente, no es solo un wrapper sino un entorno de ejecución completo dentro de la base de datos. Consiste en varias capas:
- SQL DSL: un conjunto de operadores para describir el grafo de tareas.
- Background Worker: un proceso en segundo plano dentro de Postgres que gestiona la ejecución.
- Duroxide: un motor de orquestación (también un desarrollo de Microsoft) que maneja la repetición determinista y los checkpoints.
Curiosamente, los autores eligieron un enfoque "nativo de SQL". Describes la lógica directamente en la consola o migración, usando operadores especiales como ~> o |=>.
Características principales
Aquí hay tres cosas que realmente simplifican tu vida.
Tolerancia a fallos sin servicios externos
No necesitas Redis para colas o instancias separadas de Temporal. Todo vive directamente en las tablas df.* y duroxide.*. Los datos y la lógica de control están en el mismo entorno transaccional. Esto elimina el problema clásico de los sistemas distribuidos donde una tarea existe en la cola pero los cambios en la base de datos aún no se han confirmado.
Ejecución paralela y fusión
Usando operadores, puedes fácilmente hacer "fork" de la ejecución de tareas en múltiples flujos paralelos y luego esperar a que completen. El README tiene un ejemplo claro: contar usuarios, pedidos e ingresos simultáneamente, y luego consolidar todo en un único paso de reporte.
Integración con sistemas externos
La extensión tiene una función df.http(). Esto significa que puedes llamar a microservicios externos o APIs de redes neuronales directamente desde un proceso de larga duración. Si la API devuelve un error 500, pg_durable puede esperar y reintentar sin bloquear toda la operación de la base de datos.
Ejemplo de código
Así es como se ve crear una tarea simple directamente en SQL:
-- Запускаем процесс: берем 100 необработанных документов и обновляем их статус
SELECT df.start(
'SELECT id FROM documents WHERE processed = false LIMIT 100' |=> 'batch'
~> 'UPDATE documents SET processed = true WHERE id = ANY($batch)'
);
Este código creará una instancia de Workflow que está garantizada para ejecutarse. Si el UPDATE falla, el sistema sabrá exactamente qué batch de datos estaba intentando procesar.
Dónde resulta útil
Veo varios escenarios donde pg_durable te ahorrará mucho tiempo.
Primero, pipelines de IA. Si necesitas procesar miles de filas a través de embeddings y guardarlas en pgvector, esta es la herramienta ideal. El chunking de texto, las llamadas a la API de OpenAI y los upserts a la base de datos se empaquetan en un pipeline confiable.
Segundo, procesamiento de datos a gran escala (ETL). En lugar de escribir procedimientos masivos de PL/pgSQL que fallan cuando se acaba el WAL, puedes dividir el trabajo en pasos pequeños con checkpoints.
Tercero, automatización de administración. Por ejemplo, verificar el hinchamiento de tablas, enviar una notificación y esperar aprobación — todo esto puede describirse como una Durable Function.
Matices y limitaciones
El proyecto está en estado de Preview. Esto significa que es muy temprano para llevarlo a producción, pero es perfecto para herramientas internas.
Limitaciones importantes: necesitas PostgreSQL 17 o 18. Si estás en versiones anteriores, necesitarás actualizar. Otro punto es la seguridad. Por defecto, todas las funciones en shared_preload_libraries requieren derechos de superusuario para la configuración, aunque los desarrolladores han proporcionado un sistema de permisos a través de df.grant_usage() para roles regulares.
El sistema está optimizado para SQL. Si necesitas lógica de negocio compleja con bucles complicados en Python o Node.js, es mejor usar el motor Duroxide directamente desde tu código de aplicación. pg_durable se trata específicamente de mantener los cálculos lo más cerca posible de los datos.
Microsoft está invirtiendo activamente en Postgres (recuerda la adquisición de Citus), y pg_durable es otro paso hacia convertir la base de datos en una plataforma de aplicación completa. Si estás cansado de que tus tareas en segundo plano "se caigan" en los momentos más inconvenientes, o si estás harto de configurar orquestadores externos para pipelines simples, definitivamente échale un vistazo a este repositorio. Para comenzar, un simple contenedor Docker o Codespaces será suficiente, que ya están configurados en la pestaña Development en el repositorio.
Proyectos relacionados