Come fare in modo che PostgreSQL completi le attività in background anche dopo un riavvio
Immagina questo scenario: stai scrivendo un workflow complesso di elaborazione dati. Devi recuperare dei record, inviarli a un'API esterna, aggiornare lo stato nel database e poi avviare la generazione di report. Se il database va in crash o il server si riavvia a metà del processo, una procedura PL/pgSQL standard si limiterà ad abortire. Il risultato è dati "sospesi" e un bel mal di testa: come fai a capire a che punto si è fermato tutto e come fai a riavviarlo in sicurezza?
Di solito risolviamo questo con workaround. Configuriamo pg_cron, creiamo tabelle con code di job, scriviamo worker in Python o Go che fanno polling costante del database. Nel peggiore dei casi, trasciniamo nel progetto bestie pesanti come Temporal o Airflow. Ma gli ingegneri di Microsoft hanno deciso che tutta questa complessità era inutile e hanno rilasciato l'estensione pg_durable.
Perché gli sviluppatori ne hanno bisogno
L'idea principale dietro il progetto è dare a PostgreSQL la capacità di eseguire funzioni di lunga durata resilienti ai fallimenti. Nella terminologia degli autori, questo si chiama "Durable Execution".
Se il tuo workflow è definito tramite pg_durable, il database salverà il suo stato dopo ogni passaggio. Se il server si riavvia proprio nel mezzo dell'esecuzione di una query pesante o di una chiamata API, l'estensione riprenderà il task dall'ultimo checkpoint riuscito. Non hai più bisogno di incollare insieme cron job, worker e tabelle di stato.
Come funziona internamente
Il progetto è scritto in Rust usando pgrx. Architetturalmente, non è solo un wrapper ma un ambiente di esecuzione completo all'interno del database. È composto da diversi strati:
- SQL DSL: un insieme di operatori per descrivere il grafo dei task.
- Background Worker: un processo in background dentro Postgres che gestisce l'esecuzione.
- Duroxide: un motore di orchestrazione (anch'esso uno sviluppo Microsoft) che gestisce il replay deterministico e i checkpoint.
Interessante che gli autori abbiano scelto un approccio "SQL-native". Descrivi la logica direttamente nella console o nella migrazione, usando operatori speciali come ~> o |=>.
Funzionalità principali
Ecco tre cose che semplificano davvero la vita.
Tolleranza ai guasti senza servizi esterni
Non hai bisogno di Redis per le code o istanze separate di Temporal. Tutto risiede direttamente nelle tabelle df.* e duroxide.*. Dati e logica di controllo sono nello stesso ambiente transazionale. Questo elimina il classico problema dei sistemi distribuiti dove un task esiste nella coda ma le modifiche nel database non sono ancora state committate.
Esecuzione parallela e merging
Usando gli operatori, puoi facilmente "biforcare" l'esecuzione dei task in stream paralleli multipli e poi aspettare che completino. Il README ha un esempio chiaro: conta utenti, ordini e ricavi simultaneamente, poi consolida tutto in un singolo step di reporting.
Integrazione con sistemi esterni
L'estensione ha una funzione df.http(). Questo significa che puoi chiamare microservizi esterni o API di reti neurali direttamente da un processo di lunga durata. Se l'API restituisce un errore 500, pg_durable può aspettare e riprovare senza bloccare l'intera operazione del database.
Esempio di codice
Ecco come appare la creazione di un task semplice direttamente in SQL:
-- Запускаем процесс: берем 100 необработанных документов и обновляем их статус
SELECT df.start(
'SELECT id FROM documents WHERE processed = false LIMIT 100' |=> 'batch'
~> 'UPDATE documents SET processed = true WHERE id = ANY($batch)'
);
Questo codice creerà un'istanza di Workflow che è garantita essere eseguita. Se l'UPDATE fallisce, il sistema saprà esattamente quale batch di dati stava cercando di elaborare.
Dove può essere utile
Vedo diversi scenari dove pg_durable ti farà risparmiare molto tempo.
Primo, pipeline AI. Se hai bisogno di processare migliaia di righe attraverso embedding e salvarle in pgvector, questo è lo strumento ideale. Il chunking del testo, le chiamate API di OpenAI e gli upsert nel database sono impacchettati in una pipeline affidabile.
Secondo, elaborazione dati su larga scala (ETL). Invece di scrivere procedure PL/pgSQL mostruose che cadono quando il WAL si esaurisce, puoi spezzare il lavoro in piccoli passi con checkpoint.
Terzo, automazione dell'amministrazione. Per esempio, controllare il bloat delle tabelle, inviare una notifica e aspettare l'approvazione — tutto questo può essere descritto come una Durable Function.
Sfumature e limitazioni
Il progetto è in stato di Preview. Questo significa che è troppo presto per portarlo in produzione, ma è perfetto per strumenti interni.
Limitazioni importanti: hai bisogno di PostgreSQL 17 o 18. Se sei su versioni più vecchie, dovrai fare l'upgrade. Un altro punto è la sicurezza. Di default, tutte le funzioni in shared_preload_libraries richiedono diritti di superuser per la configurazione, anche se gli sviluppatori hanno fornito un sistema di permessi tramite df.grant_usage() per i ruoli normali.
Il sistema è ottimizzato per SQL. Se hai bisogno di logica di business complessa con loop intricati in Python o Node.js, è meglio usare il motore Duroxide direttamente dal codice della tua applicazione. pg_durable riguarda specificamente mantenere i calcoli il più vicino possibile ai dati.
Microsoft sta investendo attivamente in Postgres (ricordi l'acquisizione di Citus), e pg_durable è un altro passo verso la trasformazione del database in una piattaforma applicativa completa. Se sei stanco dei tuoi task in background che "spariscono" nei momenti più inopportuni, o se sei stufo di configurare orchestratori esterni per pipeline semplici, dai sicuramente un'occhiata a questo repository. Per iniziare, un semplice container Docker o Codespaces sarà sufficiente, che sono già configurati nel tab Development nel repository.
Progetti correlati