Seus workflows devem viver no Postgres? Um framework de decisão para CTOs após o pg_durable

Por Diogo Hudson Dias
PostgreSQL server rack with a laptop showing SQL queries and job queue metrics in a modern data center

Você não precisa de mais um sistema distribuído se o seu banco de dados consegue dar conta do recado. Mas você também não precisa do seu banco fazendo tudo. A decisão da Microsoft de abrir o código de um mecanismo de execução durável no próprio banco para Postgres (pg_durable) coloca essa tensão na sua mesa hoje. A proposta é sedutora: menos peças móveis, consistência transacional e observabilidade nativa em SQL. O risco é igualmente real: domínios de falha ampliados, tabelas quentes com amplificação de escrita e trabalho de DBA disfarçado de simplicidade.

Este post traz um framework de decisão pragmático. Quando mover workflows para dentro do Postgres? Quando manter um orquestrador externo como Temporal, Cadence, Conductor, Argo ou AWS Step Functions? E, se você optar por ir no banco, como evitar que isso vire a próxima narrativa do seu incidente?

O problema que o pg_durable quer resolver

Workflows duráveis precisam de três coisas:

  • Estado que sobrevive a reinícios de processos e máquinas.
  • Timers e retries com backoff que não somem durante deploys.
  • Execução exatamente uma vez ou efeitos idempotentes atrelados às escritas de negócio.

A maioria dos times improvisa isso com uma fila, uma tabela de jobs e algumas chaves de idempotência “best effort”. Funciona — até não funcionar. Orquestradores externos resolvem isso, mas adicionam um novo sistema distribuído para operar, aprender e pagar. A execução durável no banco diz: deixe o Postgres manter a máquina de estados do workflow e os timers, e deixe seus workers puxarem etapas com semântica transacional. Você reduz saltos de rede e acopla escritas de negócio e transições de workflow em um único commit.

Primeiros princípios: os quatro eixos que decidem isso

1) Formato do workload

Mapeie o que realmente passa pelos seus workflows:

  • Throughput: média e p95 de workflows iniciados por segundo; etapas executadas por segundo.
  • Duração da etapa: CPU‑bound no seu código (10–500 ms), esperas de I/O para APIs de terceiros (100 ms–10 s) ou humano no loop (minutos–dias).
  • Fan‑out: um workflow → N etapas paralelas? Quão grande pode ser N?
  • Tamanho do payload: entrada/saída de etapa persistida por transição (bytes vs KB vs MB).

Por que importa: o Postgres é excepcional em milhares de pequenas transações por segundo com tamanhos de linha previsíveis. Ele fica menos feliz com partições quentes de alta rotatividade, logs append‑only sem limite no cluster principal ou payloads de megabytes no histórico do workflow. Como regra de bolso, sistemas de fila em Postgres bem afinados (por exemplo, pg_boss, pgbmq, padrões com SKIP LOCKED) normalmente sustentam 5–20 mil jobs/s em um único bom servidor com SSDs. Dá para extrair mais com particionamento e VACUUM agressivo, mas agora você virou engenheiro de filas. Orquestradores externos passam disso sem pressionar seu banco primário.

2) Proximidade dos dados e acoplamento transacional

Suas etapas de workflow alteram linhas no mesmo Postgres que guarda a verdade do seu domínio? Se sim, orquestração no banco é convincente. Você pode fazer a escrita de negócio e a transição do workflow acontecerem na mesma transação. Isso elimina complexidade de outbox/relay e fecha uma classe de janelas de corrida. Se a maioria das etapas chama APIs externas ou outros datastores, o valor do acoplamento cai e você passa a carregar no Postgres estado que não está co‑localizado com o trabalho real.

3) Domínios de falha e raio de impacto

Mover a orquestração para dentro do Postgres amplia o raio de impacto de um incidente de banco. Um surto de write‑ahead log (WAL) causado por uma tempestade de retries mal configurada pode degradar o mesmo cluster de que suas queries de produto dependem. Se sua postura de RTO/RPO já trata o primário como joia da coroa, você quer tempestades de workflow morando lá? Orquestradores externos custam um plano de controle separado que pode falhar independentemente — o que é bom quando seu banco está em chamas.

4) Restrições de plataforma e capacidade do time

Checagens de realidade:

  • Limitações de Postgres gerenciado: alguns provedores restringem extensões C. RDS e Aurora Postgres permitem um subconjunto. Verifique se o pg_durable roda onde você roda Postgres.
  • Multi‑região: se você precisa de ativo‑ativo entre regiões, orquestradores com seu próprio modelo de replicação podem ser mais fáceis do que fazer multi‑write de estado de workflow em Postgres.
  • Músculo de Ops: operar bem o Temporal ou o Step Functions não é de graça. Nem se tornar expert em autovacuum, HOT updates e tabelas de jobs particionadas.

Onde a execução durável no banco brilha

1) Workflows de baixa latência, locais à linha

Pense em checagens antifraude em uma linha de checkout, recomputação de entitlements quando um usuário muda de plano ou refresh de views materializadas. O caminho de execução é “tocar poucas linhas, emitir um evento, agendar um retry”. Você quer cada etapa idempotente e comprometida na mesma transação da mudança na linha. No banco, vence em simplicidade, consistência e latência (um salto de rede a menos).

2) Controle de custo apertado e times enxutos

Um sistema horizontalmente escalável a menos para operar significa menor custo cognitivo e em dólares. Se o pico é abaixo de 5 mil execuções de etapas/s e os payloads são pequenos, é difícil bater o Postgres em TCO. Você paga por IOPS maiores e folga de armazenamento, em vez de pagar por um cluster de orquestrador ou por transição de estado em um serviço de nuvem.

3) Compliance e auditabilidade via SQL

Histórico de etapas e decisões vivem em tabelas que você pode fazer join, snapshot e exportar sob seu regime de compliance existente. Nada de um lago de auditoria separado para reconciliar. Auditorias SOC 2 e ISO 27001 ficam mais fáceis quando você comprova controle com queries SQL.

Onde orquestradores externos ainda vencem

1) Fan‑out agressivo, cauda longa, etapas humanas

Centenas de ramificações paralelas, etapas que esperam horas por callbacks e aprovações humanas te empurram para o mundo da orquestração. As timer wheels e o event sourcing do Temporal foram projetados para isso. Empurrar esse formato para o Postgres sem sufocar o resto do seu workload é uma arte que você não deveria precisar aprender.

2) Fronteiras de time e isolamento de serviços

Se você é um scale‑up multi‑times, um orquestrador externo vira uma plataforma com APIs bem definidas, cotas e justiça multi‑tenant. Colocar o estado de workflow de todo mundo em um único schema pode criar incidentes de vizinho barulhento e brigas políticas sobre configurações de VACUUM.

3) Atividade multi‑região e proliferação de permissões na nuvem

SLAs globais e workflows entre regiões ficam mais fáceis de raciocinar quando o orquestrador abstrai a replicação. E quando suas etapas precisam de credenciais de nuvem para uma dúzia de serviços, manter segredos e IAM no escopo do orquestrador costuma ser melhor do que empurrar mais responsabilidade para o limite do banco.

Os custos ocultos dos workflows no banco

Amplificação de escrita e orçamento de VACUUM

Cada transição de etapa é pelo menos uma escrita e, muitas vezes, duas (claim + complete) mais uma escrita de timer. Em 2k etapas/s com linhas de 300 bytes em média, você está gerando na ordem de 600 KB/s de churn de tabela antes do overhead do WAL. O multiplicador do WAL pode facilmente ser de 2–4x dependendo dos índices. Isso dá 1.2–2.4 MB/s de WAL, o que se traduz em 100–200 GB/dia. Planeje IOPS e armazenamento de acordo. Depois, planeje o VACUUM: se o atraso do autovacuum crescer, os tuplos mortos incham, HOT updates degradam e réplicas começam a atrasar.

Partições quentes e design de índices

Timers criam um hotspot em torno de “next_due_at”. Você vai precisar de índices parciais, bucketing (por exemplo, por minuto) e particionamento para grandes implantações. Essa é engenharia que você pagaria ao seu fornecedor de orquestrador com um cartão de crédito.

Replicação lógica e efeitos colaterais no CDC

Tabelas de workflow de alto churn podem dominar seu fluxo de replicação lógica e afogar consumidores downstream. Use filtros de publicação para excluir schemas de workflow do CDC, a menos que seja obrigatório. Se você não puder excluí‑los, considere um cluster Postgres separado para o estado de orquestração.

Observabilidade que você precisará construir

SQL torna o debug ad hoc delicioso. Mas você ainda precisa de métricas de nível de serviço: contagem de início/fim de etapas, retries, tamanho de DLQ, atraso da roda de timers, saturação de executores. Se a extensão não exporta isso, você terá que construir. Esse trabalho permanece seja nearshore ou in‑house.

Um framework de decisão concreto

Atribua a cada afirmação uma nota de 0–2. Some sua pontuação.

  1. 80%+ das etapas do workflow leem/escrevem linhas no mesmo cluster Postgres do seu produto core.
  2. p95 de runtime da etapa abaixo de 500 ms; 99º abaixo de 5 s; payloads abaixo de 10 KB.
  3. Pico sustentado de transições abaixo de 5k/s; fan‑out tipicamente abaixo de 32 ramificações.
  4. Região única ou ativo‑passivo é aceitável; RTO de minutos, não segundos.
  5. Seu provedor de Postgres permite as extensões necessárias; seu time sabe ajustar autovacuum e particionamento.
  6. Compliance se beneficia de auditoria de histórico de workflow nativa em SQL.
  • 10–12: Execução durável no banco provavelmente é o padrão certo. Mantenha disciplina.
  • 6–9: Misto. Comece no banco para fluxos locais à linha; separe fluxos de cauda longa ou com grande fan‑out para um orquestrador externo.
  • 0–5: Use um orquestrador externo. O formato do seu workload ou da sua organização vai punir o banco.

Se você optar pelo in‑DB: guarda‑corpos que evitam drama às 3 da manhã

1) Schema separado, talvez um cluster separado

Mantenha as tabelas de workflow em seu próprio schema com filtros de publicação desativados por padrão. Se as réplicas de leitura do seu produto ou consumidores de CDC começarem a atrasar por causa do churn de workflow, mova o estado de workflow para um segundo cluster Postgres antes de mudar de tecnologia. Dois clusters Postgres ainda costumam ser mais simples do que aprender um orquestrador novo inteiro.

2) Particione por tempo e por tenant

Particione tabelas de jobs e histórico por bucket de due_at e, opcionalmente, por tenant. Remova partições antigas em vez de usar DELETE. Mantenha o conjunto de trabalho quente pequeno o suficiente para que o VACUUM complete bem abaixo dos seus intervalos de backoff de retry.

3) Projete para idempotência e logs de efeitos

Exija um request_id para todo efeito externo. Armazene uma tabela de log de efeitos indexada por (request_id, target). Faça todos os workers checarem isso antes de realizar um efeito. Essa é uma higiene agnóstica ao orquestrador que transforma entrega at‑least‑once em efeitos exactly‑once.

4) Coloque timers sob orçamento

Não deixe cada time agendar despertadores arbitrários. Ofereça políticas de backoff fixas e limites de retries por tipo de workflow. Exponha uma métrica de “dívida de timers”: total de timers em atraso. Alerta para crescimento. Essa métrica dirá sobre saturação de executores mais rápido do que relatos de usuários.

5) Backpressure e fairness

Use SKIP LOCKED ou advisory locks com limites por fila. Garanta que filas de alto valor não sejam famintas por grandes volumes de jobs de baixa prioridade. Defina limites de concorrência por tenant. Faça cumprir em SQL, não só no código dos workers.

6) SLOs de WAL, IOPS e VACUUM

Defina SLOs mensuráveis: por exemplo, atraso p95 de VACUUM abaixo de 5 minutos para partições quentes; geração de WAL abaixo de 3 MB/s sustentados no pico; atraso de aplicação em réplicas abaixo de 5 segundos para schemas que não são de workflow. Se você não consegue medir isso, está voando às cegas.

7) Testes de falha

Mate um worker durante uma etapa. Reinicie o primário durante uma tempestade de retries. Pause o autovacuum. Meça o tempo de recuperação e o volume de tentativas duplicadas de efeitos colaterais. Se esses exercícios assustarem, seus padrões ainda não estão seguros.

Se você ficar com um orquestrador externo: preserve os ganhos

  • Co‑loque as escritas de dados: Mesmo com Temporal ou Step Functions, mantenha o padrão de outbox próximo às suas tabelas de domínio. Faça a conclusão da etapa depender da escrita no outbox.
  • Use o orquestrador só para a cauda longa: Considere um híbrido: no banco para workflows curtos e locais à linha; externo para etapas humanas e grandes fan‑outs. Trace a linha por latência e retenção, não por time.
  • Limite o susto na fatura: Se você está em um modelo de precificação por transição, mova retries excessivamente frequentes para backoff em processo antes de expor ao orquestrador.

E quanto a agentes e workflows de IA?

Sistemas baseados em agentes são os piores clientes possíveis de workflow: cadeias longas, ramificações especulativas, retries em ferramentas instáveis e prompts de megabytes nos payloads das etapas. Resista à tentação de colocar esse estado no seu Postgres primário. Se precisar manter uma trilha de controle fina no Postgres (por exemplo, para billing ou auditoria), armazene apenas referências e metadados mínimos. Os fluxos de tokens, logs de ferramentas e scratchpads pertencem a um armazenamento de blobs indexado por um orquestrador dedicado ou por um processador de streams, não no seu banco OLTP.

Modelagem de custos: conta simples, implicações grandes

Aqui vai uma conta de guardanapo rápida para comparar opções para uma startup de porte médio:

  • No banco: Pico de 2k transições/s, 1 KB por transição de estado/log, fator de WAL ~3x → ~500 GB/mês de churn de armazenamento, mais réplicas. Assuma uma instância Postgres robusta a $2–4k/mês e 2–3 réplicas. Engenharia: 0.2–0.4 FTE de um sênior que conhece os internos do Postgres.
  • Orquestrador externo (Temporal self‑hosted): cluster de 3–5 nós, $1–2k/mês de infra, 0.3–0.6 FTE de engenheiro de plataforma. Melhor isolamento, mais software para rodar.
  • Orquestrador externo (gerenciado/PaaS): Geralmente precificação por transição. A $0.25 por milhão de transições de estado (ilustrativo; verifique com seu fornecedor), 5B transições/ano dá $1.25M. Muitos times subestimam essa rubrica até a conta chegar.

Nenhum desses números é “o certo” para você. Mas eles enquadram a decisão: você vai pagar em computação, em expertise ou em margem do fornecedor?

Migrações sem downtime

De filas DIY para execução durável no banco

  1. Introduza a extensão e as novas tabelas em um schema separado.
  2. Faça dual‑write dos claims de etapa para a tabela de jobs antiga e a nova para um subconjunto canário.
  3. Troque os consumidores (workers) por tipo de workflow atrás de flags.
  4. Endureça VACUUM, atraso de timers e checagens de idempotência; depois vire o padrão.
  5. Faça backfill ou arquive o histórico antigo de jobs em armazenamento barato e remova as partições quentes.

Do in‑DB para um orquestrador externo (a rota de fuga)

  1. Adicione uma camada de adaptador que traduza sua linha de estado de workflow em input de workflow do orquestrador.
  2. Para novos trabalhos, envie externamente, mas mantenha um ponteiro no Postgres para auditoria.
  3. Para trabalhos em execução, exporte timers como timers do orquestrador em limites de handoff seguros (após a conclusão de uma etapa).
  4. Mantenha os timers no banco desabilitados, porém recuperáveis, por 1–2 ciclos de release caso precise reverter.

Uma nota sobre execução nearshore

Se você é um CTO nos EUA trabalhando com um time nearshore brasileiro, a execução durável no banco pode ser um multiplicador de velocidade quando o time já domina Postgres. A janela de sobreposição (6–8 horas) é suficiente para iterar rapidamente em schema e semântica de execução, e você evita esperar por um novo contrato de plataforma ou integração de SSO. A troca é que você precisa escrever SLOs operacionais e runbooks cedo — caso contrário, a mentalidade de “é só SQL” deixará trabalho invisível que aparece na pior hora.

Conclusão

pg_durable coloca a execução durável no banco ao alcance de times que nunca montariam o Temporal. Isso é bom para entregar. É perigoso para times que confundem menos contêineres com menos complexidade. Decida pelo formato do seu workload e pelo seu modelo de falhas — não pela moda.

Principais pontos

  • Workflows no banco brilham para etapas de baixa latência, locais à linha, com acoplamento transacional forte. Eles reduzem cola de outbox/relay e uma peça móvel.
  • Orquestradores externos vencem para fan‑out agressivo, esperas longas, atividade multi‑região e isolamento multi‑times.
  • Modele custos: Postgres paga em WAL/IOPS e tempo de DBA; orquestradores pagam em clusters ou em dólares por transição.
  • Se for no banco, faça orçamento para VACUUM, particionamento, justiça de timers e SLOs de WAL. Coloque timers e retries sob orçamento.
  • Mantenha uma rota de fuga limpa: schema separado, canários com dual‑write e limites claros de handoff de volta para um sistema externo caso você ultrapasse o Postgres.

Ready to scale your engineering team?

Tell us about your project and we'll get back to you within 24 hours.

Start a conversation