1 pontos por GN⁺ 7 시간 전 | 1 comentários | Compartilhar no WhatsApp
  • 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

Saiba mais

1 comentários

 
GN⁺ 7 시간 전
Comentários do Hacker News
  • absurd, de Armin Ronacher, é uma implementação de workflow durável para Postgres
    https://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

    • Se você não precisa de um throughput muito alto, acho que absurd e 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 complexidade
    2) 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

    • Fiquei curioso sobre como lidam com atualizações de schema. Vocês migram os jobs ou tratam o deploy dos workers de alguma forma específica?
  • 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.

    • Não usei DBOS, mas usei Temporal no trabalho atual e no anterior, então lidei com ele por cerca de 1,5 ano no total
      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ção
      Alé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
    • Achei o Temporal excessivamente complexo, mas, como foi dito, a melhor parte é que ele impõe boas práticas de engenharia
      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
    • Acabamos de lançar uma abordagem de armazenamento externo para resolver o problema de payloads grandes
      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
    • Opero uma implantação grande de Temporal on-premises, e esta conta é temporária porque tenho medo de ser identificado
      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
    • Estamos usando dbos para workflows gerados por IA e processamento de arquivos de vídeo
      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 nossa produção usamos Redis para a fila, mas também já vi usuários usando Postgres e MySQL como fila
  • 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

    • O Temporal.io chega relativamente perto disso com recursos de query, signal e update
      Mas não tenho certeza se isso é uma integração completa
    • Já houve tentativas assim, mas uma stored procedure de mil linhas é realmente um pesadelo
    • Isso parece convex.dev ou https://spacetimedb.com/. Não uso nenhum dos dois diretamente
  • 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

  • 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 LOCKED para que os workers possam competir com segurança
    Se 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 lock

    • De qualquer forma, nesse caso advisory lock costuma ser preferível. Segurar muitos SELECT FOR UPDATE não escala bem
      Edit: revendo agora, parece que o conselho mudou para o contrário
    • Também dá para simplesmente reservar o registro com um ID de actor
  • 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