pg_durable - Funções SQL duráveis para PostgreSQL
(github.com/microsoft)- Extensão de funções duráveis que lida com retries, agendamento, fan-out paralelo e desvios condicionais dentro do PostgreSQL usando apenas uma pequena DSL em SQL
- Funciona apenas com Postgres e background workers, sem contêineres nem serviços externos
- Todas as etapas registram checkpoints de estado no PostgreSQL, permitindo retomar do ponto de interrupção mesmo após falha, reinício ou perda de conexão
- Sem precisar implementar manualmente gerenciamento de filas, rastreamento de estado, recuperação de falhas, coordenação de etapas e retries: basta escrever SQL e o motor de orquestração faz o resto
- Substitui tarefas que exigiriam mais de 300 linhas de boilerplate por uma única chamada DSL, com uso imediato em código aberto no PostgreSQL 17
Visão geral e valor principal
- Função durável embutida no Postgres, à prova de falhas (crash-proof), que orquestra retries, agendamento, fan-out paralelo e desvios condicionais com uma pequena DSL em SQL
- Funciona apenas com Postgres + background workers, sem infraestrutura adicional, contêineres separados ou serviços externos
- Atua como um motor de orquestração responsável por gerenciamento de filas, rastreamento de estado, recuperação de falhas, coordenação de etapas e retries; o usuário só escreve SQL
Como seria implementar sem pg_durable
- Para executar 3 agregações em paralelo, atualizar um dashboard e ainda incluir retries e recuperação de falhas, seriam necessárias mais de 300 linhas de boilerplate
- Itens que precisariam ser construídos manualmente: configuração e estrutura da fila, gerenciamento de workers e polling, processamento de mensagens e rastreamento de estado, tratamento de erros e retries, coordenação manual de etapas
- O código de exemplo inclui várias tabelas de estado como
job_queue,job_results,job_state,workflow_steps,step_variables,scheduled_jobs, além de worker de polling, progresso do workflow, recuperação de falhas, coordenador de execução paralela, passagem de variáveis, agendamento e funções de limpeza - O cálculo de
next_runno agendamento exige adicionalmente uma biblioteca externa de parser de cron
Como fica com pg_durable
- A mesma agregação paralela + atualização de dashboard pode ser expressa com uma única chamada
df.start(), usando o operador¶ fan-out e~>para join- Exemplo: 3 consultas se ramificam em paralelo e depois convergem na etapa
refresh dashboardpara gerar o resultado - No exemplo de execução ao vivo, após 3 etapas em paralelo e um join, o dashboard fica pronto de forma durável em apenas 1,9 segundo
- Exemplo: 3 consultas se ramificam em paralelo e depois convergem na etapa
- Gerenciamento de filas, rastreamento de estado, recuperação de falhas, coordenação de etapas e retries são todos tratados pelo pg_durable
Principais recursos
-
Durável por padrão
- Todas as etapas registram o estado como checkpoint no PostgreSQL, então o workflow sobrevive a falhas, reinicializações e perdas de conexão
- Retoma exatamente do ponto em que parou
-
Retries automáticos
- Lógica de retry embutida para tarefas instáveis; se uma etapa falhar, apenas ela é tentada novamente, enquanto o restante do workflow continua
- Não há necessidade de código manual de tratamento de erros
-
Observabilidade completa em SQL
- Todo o estado do workflow é armazenado em tabelas do Postgres, permitindo consultar histórico de execução, verificar saídas de etapas e depurar falhas com SQL padrão
- Sem necessidade de dashboard externo
-
Execução paralela
- Com o operador
&oudf.join(), tarefas independentes podem fazer fan-out, executando agregações, chamadas de API e etapas de ETL simultaneamente com coordenação automática
- Com o operador
Padrões que podem ser criados
-
Pipelines de ETL
- Encadeia cleanup → transform → load com garantia de sequência; cada etapa espera a anterior e, em caso de falha, o pipeline é interrompido de forma limpa (
~> sequence,|=> variables)
- Encadeia cleanup → transform → load com garantia de sequência; cada etapa espera a anterior e, em caso de falha, o pipeline é interrompido de forma limpa (
-
Agregação paralela
- Conta usuários + soma receita + verifica inventário ao mesmo tempo, fazendo fan-out com várias consultas e aguardando a conclusão total (
&,df.join())
- Conta usuários + soma receita + verifica inventário ao mesmo tempo, fazendo fan-out com várias consultas e aguardando a conclusão total (
-
Processamento de pedidos
- Captura o ID do pedido e o repassa pelas etapas de validação, processamento e conclusão, com fluxo automático de variáveis entre etapas (
|=> capture,$var substitution,df.sleep())
- Captura o ID do pedido e o repassa pelas etapas de validação, processamento e conclusão, com fluxo automático de variáveis entre etapas (
-
Jobs agendados
- Faz polling de APIs, arquivamento de registros e sincronização de dados com cron; o loop roda permanentemente e sobrevive a reinicializações (
@> loop,df.wait_for_schedule())
- Faz polling de APIs, arquivamento de registros e sincronização de dados com cron; o loop roda permanentemente e sobrevive a reinicializações (
-
Desvios condicionais
- Verifica tarefas pendentes, número de linhas ou flags para decidir entre processar ou pular, com a lógica de desvio no SQL, não na aplicação (
df.if(),?> conditional)
- Verifica tarefas pendentes, número de linhas ou flags para decidir entre processar ou pular, com a lógica de desvio no SQL, não na aplicação (
-
Validação em múltiplas etapas
- Busca dados → valida esquema → verifica regras de negócio → aprova/rejeita, com checkpoints em cada etapa para não perder progresso em caso de falha
-
Manutenção de banco de dados
- Detecta fatores que bloqueiam autovacuum, bloat de tabelas e risco de wraparound, expondo para revisão e depois corrigindo de forma durável mesmo após reinicializações (
?> conditional,df.wait_for_signal(),@> loop)
- Detecta fatores que bloqueiam autovacuum, bloat de tabelas e risco de wraparound, expondo para revisão e depois corrigindo de forma durável mesmo após reinicializações (
-
Azure Functions & HTTP
- Com
df.http(), chama Azure Functions ou endpoints HTTPS permitidos diretamente do SQL para processar inline chunking de documentos, enriquecimento de linhas e classificação de registros
- Com
-
Aprovação com humano no loop
- Aprova automaticamente tarefas rotineiras e pausa tarefas de alto risco até receber um sinal de aprovação humana (grandes faturas, operações destrutivas) (
df.wait_for_signal(),df.if())
- Aprova automaticamente tarefas rotineiras e pausa tarefas de alto risco até receber um sinal de aprovação humana (grandes faturas, operações destrutivas) (
Suporte de autoria com IA
- Ao descrever o workflow em inglês simples, o Copilot gera o SQL correto de função durável, sem necessidade de aprender a sintaxe
- O repositório inclui a skill reutilizável de agente
pg-durable-sql, que ensina ao GitHub Copilot e outros agentes como gerar corretamente SQL com operadores, substituição de variáveis, loops e joins paralelos
Disponível como código aberto
- Disponível como código aberto completo, sem lista de espera nem lock-in; basta clonar o repositório, compilar e executar imediatamente no seu próprio PostgreSQL
- É possível aplicar orquestração durável em notebooks, servidores ou nuvem
Opção gerenciada no Azure HorizonDB
- O Azure HorizonDB é o novo serviço de PostgreSQL em nuvem da Microsoft, com pg_durable embutido, preservando as funções duráveis que você escreveu enquanto adiciona escala empresarial, segurança e IA
- Até 3× mais desempenho, autoescalonamento de armazenamento até 128 TB e scale-out de computação de até 3.072 vCores
- Microsoft Defender para detecção de ameaças em tempo real e Microsoft Entra ID para gerenciamento de identidade
- Busca vetorial com Filtered DiskANN, ranking semântico e curadoria de modelos de IA dentro do banco de dados
- Espelhamento quase em tempo real com Microsoft Fabric, integração com VS Code e conexão com GitHub Copilot
-
Pipeline de IA embutido
- O HorizonDB adiciona um pipeline de IA gerenciado de ponta a ponta sobre a execução durável do pg_durable, com cada etapa tendo checkpoint, retry e segurança contra falhas
- Fluxo das etapas: Ingest (carregamento de documentos/dados) → Chunk (divisão de conteúdo) → Embed (vetorização) → Index (armazenamento no DiskANN) → Serve (busca/ranking)
1 comentários
Comentários do Hacker News
2026 parece que será o ano das filas em Postgres: há movimentos como DBOS[0] e pgQue[1], e é legal ver a comunidade criando esse tipo de opção
Ainda assim, como ex-engenheiro de aplicações, prefiro que a lógica da fila fique no código e no Git. Talvez eu mude de ideia com as ferramentas certas
[0]: https://www.dbos.dev/
[1]: https://github.com/NikolayS/pgque
Fico me perguntando como fazem versionamento, depuração, testes e releases. Gosto da ideia de manter tudo em um só lugar por causa da localidade dos dados e da simplicidade da stack, mas dá a sensação de perder muito conhecimento útil sobre como fazer isso “do jeito certo”
Foi por isso também que eu realmente detestava o fato de, no Supabase, qualquer coisa minimamente complexa exigir a criação de funções no Postgres. Dito isso, em uma startup anterior nós mesmos construímos uma fila de tarefas simples sobre Postgres, e algo como pgQue provavelmente teria sido uma versão bem mais refinada disso
Como até extensões multi-master não são algo para usar de imediato nem totalmente seguro, fico receoso de colocar tarefas complexas com muita escrita que acelerem a necessidade de expandir o banco de dados
Em alguns casos, isso era empurrado para dentro das migrations do Django para que, na configuração local, os triggers fossem instalados no banco de dados local
Isso tem cheiro de stored procedures. São difíceis de testar unitariamente e de versionar, e a lógica de negócio fica escondida dentro do banco de dados, virando um “cérebro oculto”
Também é difícil isolar workloads barulhentos, não há observabilidade, e toda a pressão de escalabilidade vai parar no Postgres. Falta entrada e saída, especialmente para coisas como chamadas de API. Para tarefas que rodam só dentro do banco local, tudo bem, mas parece ter uso limitado
Claro, é preciso ter um procedimento adequado de upgrade do banco de dados. Se os membros da equipe executam migrations SQL arbitrárias como root, aí vocês vão sofrer
Teste unitário também é tão possível quanto qualquer outro teste SQL; só é preciso subir o banco de dados. Se você não consegue testar stored procedures, isso significa que não tem como testar o próprio SQL, e esse é o problema real
A alternativa às stored procedures não é não colocar lógica de negócio nenhuma no banco de dados, e sim muitas vezes acabar com SQL espalhado pela base de código, difícil de testar, ruim de versionar e encapsular, e desnecessariamente lento
Sobre observabilidade, há certa razão nisso: investigar problemas de SQL costuma exigir mais esforço manual do que na maioria das linguagens de programação. Mas, se stored procedures estão causando problemas de entrada/saída e de escalabilidade, então estão sendo usadas de forma errada; quando usadas corretamente, muitas vezes reduzem bastante a entrada/saída e melhoram a escalabilidade
Se entendi direito, Absurd, criado pelos desenvolvedores do harness de LLM da Pi, parece ir na direção de reduzir ao máximo o acesso puro ao banco de dados. Ainda estou começando a olhar esse tema
https://github.com/earendil-works/absurd
Claro, não conheço todos os detalhes, então estou genuinamente curioso
Em “quando não usar”, está escrito “quando o workflow estiver majoritariamente fora do Postgres e se estender por vários sistemas heterogêneos”; se for esse o caso, então não entendo como este projeto pode ser comparado a algo como Temporal
Fico me perguntando se estou entendendo errado a limitação implícita nessa recomendação
Pode até ser uma conquista tecnicamente interessante, mas ler SQL assim é bem estranho
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');Na empresa estamos presos ao Azure e seguimos esperando o Azure PostgreSQL alcançar os recursos modernos
Por exemplo, não dá para usar isto: https://www.paradedb.com/blog/hybrid-search-in-postgresql-th...
Também não há suporte para vetores ultralargos de alta dimensionalidade. É bom ver o pg_durable sendo lançado como open source, mas seria bom começarem adotando ao menos os recursos básicos que no AWS já vêm como algo natural
Para ser transparente, sou mantenedor do pg_textsearch e agora estou na Azure. Não entendi exatamente o comentário sobre suporte a vetores; queria saber se você está procurando algo além de pgvector + diskann oferecidos pela Azure
No caso de busca híbrida (BM25 + vetor), o pg_search do ParadeDB também não é um recurso nativo da AWS; você precisa hospedá-lo por conta própria no EC2. No Azure PostgreSQL, tornamos nativo o pg_textsearch, que fornece o mesmo modelo de ranking BM25, e o principal contribuidor hoje está na equipe do Azure Postgres
Documentação: https://learn.microsoft.com/en-us/azure/horizondb/ai/full-te...
Em vetores de alta dimensionalidade, na verdade estamos à frente. O pgvector com HNSW tem limite de 2.000 dimensões, mas o Azure oferece pgvector para armazenamento e busca vetorial e, para workloads grandes e de alta dimensionalidade, fornece o pg_diskann, o índice vetorial baseado em grafos da Microsoft. Ele suporta até 16.000 dimensões e também oferece filtragem avançada dentro do índice, avaliando cláusulas WHERE durante a travessia do grafo, sem perder recall em condições seletivas
pgvector: https://learn.microsoft.com/en-us/azure/horizondb/ai/vector-...
Suporte do DiskANN para alta dimensionalidade: https://learn.microsoft.com/en-us/azure/horizondb/ai/vector-...
Esses recursos estão disponíveis hoje no Azure PostgreSQL, especialmente no Azure HorizonDB Preview. Se houver um workload específico, dá para analisar com mais detalhes
Isso parece uma solução equivocada para um problema antigo que agendadores de DAG como o Apache Airflow já resolvem há muito tempo
Acho estranho querer armazenar o fluxo de controle no banco de dados em vez de no código. Não estou tentando diminuir o projeto, só ainda não entendi bem
Este projeto parece voltado para casos de uso mais específicos de banco de dados. A vantagem provavelmente é poder acompanhar o estado exato das tarefas no próprio banco, sem precisar confrontar os logs do workflow com o codebase e seguir linha por linha. A carga e a latência também devem ser menores, e ainda há o efeito operacional de ter um componente a menos para subir
[1] https://learn.microsoft.com/en-us/azure/durable-task/common/...
Em contraste, embora não pareça funcionar assim no momento, esta abordagem poderia se autoajustar com feedback de desempenho quase em tempo real, sem o custo de latência de ida e volta
Mesmo lendo a documentação e os exemplos, algumas coisas ainda não ficam claras. Fiquei curioso sobre como
df.wait_for_schedule()funcionaSe for chamado pela aplicação, ele é idempotente? Se eu executar duas vezes com os mesmos parâmetros, dispara o tick duas vezes? É algo para chamar manualmente uma vez no console de consultas, ou para rodar como parte de um script de migração? Também queria entender se
timed_outno exemplo[0] é uma constante fixa retornada em caso de timeout. E também não fica claro de imediato como funciona o tratamento de erros ou exceções[0] https://github.com/microsoft/pg_durable/blob/main/examples/i...
df.start()é chamado, ele cria uma função durável e ao mesmo tempo inicia sua execução. Essa chamada retorna um ID de instância que representa essa execução e que pode ser usado depois para referenciá-laDentro dessa função durável,
df.wait_for_signal()é chamado, e essa chamada só é executada exatamente uma vez dentro daquela instância da função, então duplicação não é possível. Se a própria chamada dedf.start()der timeout e for executada de novo, pode haver duplicação, mas nesse caso uma instância de função diferente será criadaSe ocorrer um erro não tratado durante a execução do SQL, a instância da função falha, e o estado registra exatamente o erro que aconteceu
Você poderia explicar por que usar isso em vez de uma ferramenta de orquestração fora do banco de dados? Mesmo lendo o README e os exemplos, ainda não entendi bem
Como não é preciso sincronizar backups com outros componentes que pertencem ao mesmo armazenamento de dados, isso é bom para pipelines de ETL ou trabalhos em forma de máquina de estados. Se o ETL for majoritariamente SQL, também ajuda o fato de o trabalho real ser executado no mesmo servidor
Quando todo o estado está em um único banco de dados, também há mais chance de obter backups consistentes
Em https://transport.data.gouv.fr, eles usam Postgres para esse tipo de coisa, e isso ajuda em um app Elixir que faz bastante processamento. Ainda não conheço bem o pg_durable, mas já usei ou implementei soluções parecidas, então faz sentido para mim
O banco de dados já não é uma das infraestruturas mais difíceis de escalar? Não entendo por que alguém iria querer colocar tarefas de longa duração nele também
No fim das contas, esse tipo de workload será executado contra o banco de dados, independentemente de ser acionado ou não por um componente externo. Em pipelines de dados ou IA, também tem se tornado mais comum enviar consultas HTTP a partir do banco de dados para evitar ida e volta e pontos extras de falha causados por componentes adicionais. Ainda assim, trazer o processamento para perto dos dados ou levar os dados até o processamento é uma grande decisão de arquitetura e bastante debatida
Parece mais um caso de https://en.wikipedia.org/wiki/Inner-platform_effect que talvez nem fosse necessário se linguagens de programação ou máquinas virtuais populares já dessem suporte a determinismo, execução passo a passo mensurável e controlável, pausa do estado em runtime, serialização e desserialização, e retomada