Construindo workflows duráveis no Postgres
(dbos.dev)- Workflows duráveis fazem checkpoint do estado de execução no banco de dados, permitindo recuperar após uma falha a partir da última etapa concluída
- A orquestração externa no estilo Temporal, Airflow e AWS Step Functions adiciona um orquestrador central e um armazenamento, aumentando a complexidade
- Uma arquitetura baseada em Postgres permite que servidores da aplicação façam polling da tabela de workflows e gravem diretamente a saída das etapas, viabilizando a recuperação
- Vários servidores evitam execuções duplicadas com bloqueios e restrições de integridade, e lidam com escalabilidade, disponibilidade e observabilidade com soluções do Postgres
- Um único Postgres pode escalar verticalmente para dezenas de milhares de workflows por segundo e ainda aproveitar replicação, multi-AZ e análise via SQL
Modelo básico de workflows duráveis
- Workflows duráveis são uma abordagem em que o progresso de execução de um programa é salvo periodicamente como checkpoints no banco de dados, permitindo recuperação após travamentos ou falhas a partir da última etapa concluída
- Como o recurso de salvar e carregar em videogames, isso pode ser visto como um modelo em que o progresso do programa é “salvo” regularmente e “carregado” a partir do último checkpoint após uma falha
- Implementações comuns são sistemas de orquestração externa como Temporal, Airflow e AWS Step Functions
- Na orquestração externa, o programa durável é escrito como um workflow composto por várias etapas, e um orquestrador central coordena a execução
Como funciona a orquestração externa
- Quando um cliente envia um workflow, o orquestrador cria um registro no armazenamento de dados e o despacha para um worker executar
- Cada vez que o worker conclui uma etapa, ele envia o resultado ao orquestrador, que faz checkpoint dessa saída no armazenamento e então despacha a próxima etapa
- Se o worker travar ou falhar, o orquestrador reenvia aquele workflow para outro worker, que o reinicia a partir da última etapa registrada em checkpoint
- Essa estrutura aumenta a complexidade ao adicionar um servidor orquestrador separado à ideia central de armazenar o estado do workflow no banco de dados
Arquitetura que usa o Postgres como orquestrador
- Se a essência dos workflows duráveis é fazer checkpoint do estado do programa no banco de dados, é mais simples e eficiente usar o próprio banco de dados como orquestrador, sem um servidor orquestrador separado
- O Postgres é uma boa escolha para construir workflows duráveis por sua popularidade, escalabilidade e ecossistema rico
- Em um sistema de workflows duráveis baseado em Postgres, os servidores da aplicação executam workflows comunicando-se diretamente com o Postgres, sem passar por um orquestrador central
- O cliente envia a execução criando uma entrada na tabela de workflows do Postgres, e os servidores da aplicação fazem polling dessa tabela para retirar workflows da fila e executá-los
- Enquanto executa o workflow, o servidor grava em checkpoint no Postgres a saída de cada etapa; se o servidor em execução travar ou falhar, outro servidor recupera o workflow a partir do checkpoint
Por que um orquestrador central deixa de ser necessário
- Como os servidores da aplicação podem se coordenar via Postgres, não é necessário um orquestrador central para despachar workflows aos workers
- Os servidores podem retirar workflows cooperativamente das tabelas do Postgres, e mecanismos como cláusulas de bloqueio garantem que cada workflow seja retirado da fila por exatamente um worker
- O checkpoint da saída das etapas também é gravado diretamente pelos workers no Postgres, e não por um orquestrador
- Se vários workers tentarem executar o mesmo workflow ao mesmo tempo, as restrições de integridade do Postgres podem detectar trabalho duplicado no momento do checkpoint e fazer com que recuem
- Ao substituir o orquestrador central por Postgres ou outro banco de dados, questões como escalabilidade, disponibilidade, observabilidade e segurança podem ser tratadas com soluções nativas e bem conhecidas do Postgres
Escalabilidade e disponibilidade
- A escalabilidade e a disponibilidade de um sistema de workflows duráveis baseado em banco de dados são determinadas, em essência, pelo banco de dados subjacente
- Como é possível escalar horizontalmente adicionando mais servidores worker, a capacidade máxima depende de quão rápido o banco de dados consegue processar workflows
- Como os workers são intercambiáveis e podem recuperar o estado uns dos outros, o sistema permanece disponível enquanto o banco de dados subjacente estiver disponível
- Ao usar Postgres, a vantagem é que escalabilidade e disponibilidade são problemas estudados há muito tempo e com soluções robustas já existentes
- Um único servidor Postgres pode escalar verticalmente para processar dezenas de milhares de workflows por segundo
- Escala adicional pode ser alcançada com Postgres distribuído, como CockroachDB, ou com Postgres fragmentado
- O Postgres oferece suporte a replicação por streaming com failover automático, e serviços gerenciados normalmente já fornecem implantação multi-AZ e SLA de alta disponibilidade
- Décadas de engenharia e pesquisa acumuladas para operar Postgres em grande escala também podem ser aplicadas diretamente à operação de workflows duráveis
Observabilidade
- Em execuções duráveis baseadas em Postgres, workflows e etapas são registrados em checkpoint em tabelas do Postgres, o que permite monitorá-los em tempo real e visualizar a execução examinando esses checkpoints
- O Postgres se destaca porque praticamente toda consulta de observabilidade de workflows pode ser expressa em SQL
- Operações complexas de filtragem e análise podem ser descritas de forma declarativa em SQL, como uma consulta para encontrar todos os workflows que tiveram erros no último mês
- Isso é possível porque aproveita o modelo relacional do Postgres e décadas de pesquisa em otimização de consultas
- Muitos sistemas com modelos de dados mais simples, como os armazenamentos chave-valor usados por orquestradores externos populares, não oferecem o mesmo nível de suporte a análise baseada em SQL
- Ao armazenar dados de workflows e etapas em tabelas do Postgres e adicionar índices auxiliares para consultas analíticas rápidas, é possível obter observabilidade eficiente na execução durável
Confiabilidade e segurança
- Em execuções duráveis que usam orquestradores externos, tanto o orquestrador quanto seu armazenamento de dados se tornam pontos únicos de falha
- Como orquestrador e armazenamento coordenam diretamente a execução dos workflows, se qualquer um dos dois cair, toda a aplicação fica indisponível
- Como eles processam e armazenam workflows e checkpoints de etapas, podem ter acesso a dados sensíveis da aplicação e exigem endurecimento, controle de acesso e auditoria como infraestrutura sensível
- Em execuções duráveis baseadas em Postgres, o único ponto de falha é o próprio Postgres, e todos os dados de workflow são armazenados diretamente nele, sem passar por outros sistemas
- Se a aplicação já depende de Postgres, adotar execução durável não adiciona novos pontos de falha nem cria uma nova superfície de ataque a ser protegida
- Como o banco de dados já é infraestrutura crítica, faz mais sentido reutilizá-lo do que adicionar uma nova infraestrutura crítica apenas para orquestração
1 comentários
Comentários do Hacker News
absurd, de Armin Ronacher, é uma implementação de workflow durável para Postgreshttps://lucumr.pocoo.org/2025/11/3/absurd-workflows/
https://github.com/earendil-works/absurd
https://earendil-works.github.io/absurd/
Não usei diretamente, mas parece valer a pena comparar com outras opções
absurde sua implementação derivada em Rust,durable, são boas opções para manter o lado do cliente bem simplesÉ algo leve, então um agente de código consegue entender facilmente toda a estrutura, e, se necessário, dá para consultar o estado via query
Estamos usando dbos.dev, restate.dev e cf workflows, e no nosso Agents.md está escrito o seguinte
Restate.dev é usado na integração de pagamentos da northflank. É mais rápido que cf workflows, independente da Cloudflare e das falhas dela, e pode ser self-hosted, então não há lock-in de fornecedor
Cloudflare workflows é usado para tarefas menos críticas, como geração de relatórios CSV/PDF. É porque sai muito barato
DBOS.dev é usado para workflows que exigem mensageria atômica vinculada a transações do Postgres e precisam de 100% de confiabilidade/durabilidade. Por exemplo, preencher materialized row ou enviar e-mails/pushes importantes para comerciantes
DBOS e Restate parecem parecidos à primeira vista, mas Restate precisa de um “orchestrator” central, o que traz prós e contras, e facilita a criação junto com workers serverless de cf/vercel
Além disso, ele tem VirtualObject, então é uma boa alternativa open source sem lock-in de fornecedor ao DurableObject single-thread da Cloudflare
Há dois pontos em que o DBOS se destaca especialmente. 1) Com
dbos.enqueue_workflow, é possível fazer mensageria atômica dentro da mesma transação de banco que a lógica de negócio. Esse costuma ser o caso mais frágil em qualquer solução, então tratar isso de forma atômica e durável na mesma transação em que a lógica de negócio roda reduz bastante a complexidade2) O DBOS armazena o estado do workflow no banco, então parece fácil criar dashboards de observabilidade com metabase/looker. Seria bom se o Restate também expusesse uma instância rocksdb para conectar ao metabase
Tenho curiosidade em saber a experiência prática de quem já usou DBOS e Temporal
Já usei Temporal no passado e ele funcionava bem, mas às vezes era incômodo criar soluções por causa dos limites no tamanho de payloads de requisição ou de eventos
Ele tem a vantagem de impor boas práticas de engenharia, mas eu não gostaria de ter sempre uma lógica especial em que, se um arquivo CSV tiver mais de 2 MB, eu preciso enviá-lo para o S3, passar o link e depois baixá-lo de novo no workflow
Queria saber como é a experiência com DBOS e como ele se compara ao Temporal em termos de complexidade operacional, paridade de funcionalidades etc.
Também o uso em casa para tarefas de automação residencial que não exigem alta sensibilidade a tempo. A latência do workflow não é tão ruim, mas eu provavelmente não usaria para gatilhos que exigem resposta imediata, como eventos de detecção de movimento em casa; para timeouts como desligar algo depois de um período de inatividade, tudo bem
Gosto bastante da abordagem de colocar uma REST API fina na frente do Temporal dentro de uma VPC ou cluster Kubernetes. Assim, os gatilhos baseados em eventos não precisam se preocupar com autenticação do Temporal nem com verificação de estado do workflow, e isso ajuda a manter os eventos o mais livres possível de lógica
Por exemplo, um trigger de banco de dados atua diretamente ou coloca um evento na fila, e o handler chama uma REST API fina com os detalhes do evento necessários. A REST API pode decidir se isso deve iniciar um workflow, enviar um signal para um workflow existente ou ignorar. O padrão varia conforme o caso, mas no meu caso uso muito
SignalWithStart, ou simplesmente descarto se não vale a pena iniciar e também não existe workflow em execuçãoAlém disso, o recurso de workflows pai/filho é muito útil quando é preciso orquestrar ações independentes dentro do ciclo de vida de um único objeto, e também gosto do fato de poder cancelar quando fatores externos mudam o caminho de progresso do objeto
Para falar de forma longa e vaga, é muito poderoso, fácil de lidar e ajuda bastante a tirar a lógica de ciclo de vida de dentro da API. Se isso fica na API, dívida técnica se acumula com facilidade e a manutenção fica frágil. Concordo que ele te força a seguir práticas exemplares, em vez de jogar lógica em algum lugar aparentemente conveniente e depois descobrir armadilhas ocultas
Mas experimentei o produto Cloud e fiquei chocado com o preço. Gastei todos os 1.000 dólares de créditos grátis antes mesmo de colocar em produção. Também não queria operar um Temporal local por conta própria
Pessoalmente, acho que o melhor é pegar só as ideias da arquitetura e implementar você mesmo sobre Postgres
Não gosto 100% disso. Parece mais um acréscimo do que algo essencial, e ainda é uma versão inicial. Mesmo assim, dá para dizer que agora isso está praticamente resolvido
Depois de mais de um ano rodando isso em produção, minha visão é que o Temporal é mal projetado, lento e absurdamente pesado do ponto de vista de infraestrutura
Para trabalhos não triviais, por exemplo, com mais de 200 eventos por workflow e apenas algumas centenas rodando ao mesmo tempo ao longo do dia, você pode acabar gastando milhões de dólares em infraestrutura, e ainda assim continuar sendo ruim
Quando rodo benchmarks internos, os números são péssimos
O time de vendas também é realmente horrível e parece desesperado
Do ponto de vista do desenvolvedor, o SDK é bem bom
Não fique preso ao nexus, e se o time de vendas ligar, é melhor colocar o jurídico na sala junto
Levou um tempo para entender como migrar do Celery, mas no nosso caso valeu a pena
O Conductor OSS também faz isso muito bem https://docs.conductor-oss.org/devguide/ai/index.html
https://github.com/agentspan-ai/agentspan é essencialmente uma camada de SDK de agentes para o Conductor, que permite tornar duráveis agentes de langgraph, OpenAI, vercel e ADK sem mudanças de código, além de adicionar orquestração
Em vez de separar o armazenamento de dados, a máquina de estados, as restrições de estados válidos e a lógica que faz a transição entre estados válidos, seria bom poder integrar tudo isso em algum núcleo do estado da aplicação
Sinceramente, o Postgres já tem bastante dessa capacidade, mas ainda não vejo uma visão clara no nível do app ou do produto que forneça um conjunto comprovável de estados para os quais o app pode transitar, e que exponha isso automaticamente ao cliente de uma forma útil. Por exemplo, algo como: este usuário pode curtir este post, mas não pode editá-lo
Aos meus olhos isso parece uma forma de redes de Petri coloridas, mas ainda não existe um paradigma simples de estado de aplicação com fronteiras tão claramente bem-sucedidas quanto as de um banco de dados
Mas não tenho certeza se isso é uma integração completa
Como o DBOS não suporta Rust, implementei uma versão em Rust bem minimalista e semelhante a isso em https://github.com/tensorzero/durable
Foi bastante estável e escalável, mas, naturalmente, é preciso ter muito cuidado com a implementação SQL. Talvez seja interessante para os leitores daqui
https://flawless.dev/
Entendo completamente o conceito e concordo. É uma ótima forma de colocar esse tipo de durabilidade em um sistema de workflow
Dito isso, com meu cérebro de gamer, dá vontade de chamar isso de save scumming em massa. Muita gente provavelmente já sabe que essa abordagem funciona, mas talvez não a tenha conectado a conceitos abstratos da ciência da computação
Outra estratégia para aumentar a robustez é compor o workflow com operações idempotentes. Isso pode ser útil quando o estado do workflow é grande demais para fazer backup com facilidade. Em vez disso, você reexecuta o trabalho desde o início, e tudo até o ponto em que o progresso reaparece vira no-op
Continua me surpreendendo o quanto dá para fazer com poucas ferramentas quando o Postgres está na caixa de ferramentas
Recentemente desenvolvi uma fila distribuída e ela funciona muito bem, com bons benchmarks e sem condições de corrida nem colisões. Usei
SKIP LOCKEDpara que os workers possam competir com segurançaSe você quiser que workers em vários nós evitem colisões, também pode usar um mutex com escopo de sessão, ou seja,
pg advisory lockSELECT FOR UPDATEnão escala bemEdit: revendo agora, parece que o conselho mudou para o contrário
No Rails existem vários backends de jobs baseados em banco de dados, mas por convenção um job deve sempre fazer apenas uma coisa e, se possível, terminar bem rápido
Por causa disso, construir workflows acaba ficando meio forçado. Você enfileira o segundo job na última linha do primeiro, depois enfileira o terceiro na última linha do segundo, e assim por diante
O backend de jobs não os apresenta como um workflow conectado, e sim como jobs independentes; então, se você quiser entender o workflow em alto nível, precisa ler várias classes de job
O Rails introduziu recentemente o conceito de continuable, que permite fazer checkpoints por etapas e retomar dentro de um job, mas ainda existe uma convenção forte de manter jobs com responsabilidade única, então continua parecendo estranho para usar em workflows de verdade
Fico curioso se outras pessoas também passaram por isso e se encontraram alguma solução
Este é um padrão excelente. É melhor fazer o máximo possível dentro do banco de dados
O Spanner externo oferece change streams. O Spanner interno é diferente, principalmente porque em alguns casos há exigências extremas de escala, além de uma mistura de “já está funcionando bem” com “change streams arbitrários dão medo”
O Spanner interno permite que qualquer transação escreva entradas de fila. Aqui, a fila é basicamente uma tabela com uma noção especial de tempo. Dá para agendar a entrega, as entradas são enviadas da fila para um handler, e o handler também pode fazer escritas no banco dentro da transação de dequeue. E tudo isso mantém a mesma escalabilidade