Home Projetos & Releases BigBangcraft Blog Sobre & Carreira
DevOps & Cloud • Hub 7 min de leitura 9 visualizações
Raw .md

Durable Objects no PostgreSQL: viabilidade real e os limites de atores no banco

PD

Pedro Dalben

CTO no Integrar Plus • Publicado em 15 de September de 2026

Durable Objects no PostgreSQL: viabilidade real e os limites de atores no banco

A promessa do modelo serverless sempre foi vender facilidade operacional em troca de uma fatura variável no fim do mês. O problema dessa troca aparece quando a fatura chega com cinco dígitos por causa de uma falha de lógica banal, como um loop de alarmes desgovernado consumindo centenas de milhares de invocações sem teto de gastos configurável. Esse tipo de susto financeiro explica o entusiasmo recente em torno do SolidObjects, uma biblioteca que propõe trazer o padrão de Durable Objects, celebrizado pelo Cloudflare, diretamente para o PostgreSQL, MySQL ou SQLite.

A discussão na comunidade técnica se dividiu rapidamente em dois polos. De um lado, desenvolvedores cansados da opacidade comercial e da falta de travas financeiras nas nuvens públicas comemoram qualquer alternativa que rode no hardware que já pagam todo mês. Do outro lado, surgem críticas sobre o uso oportunista do prefixo Solid (que virou sinônimo de infraestrutura padrão no ecossistema Rails) e ceticismo sobre a legibilidade e maturidade de bibliotecas que prometem eliminar daemons distribuídos apenas adicionando tabelas SQL.

Eu concordo inteiramente com a aversão às armadilhas de faturamento das nuvens proprietárias. Opero sistemas inteiros em servidores dedicados e máquinas virtuais próprias há anos, justamente porque a previsibilidade de saber quanto a infraestrutura vai custar no dia primeiro do mês liberta a engenharia de micro-otimizações paranoicas. No entanto, discordo da ideia ingênua de que implementar atores com estado persistente no PostgreSQL seja uma troca puramente vantajosa, livre de custos operacionais ocultos.

O que o padrão realmente exige

O conceito de Durable Objects resolve um problema clássico de sistemas distribuídos: como garantir que apenas uma instância de código manipule o estado de uma entidade específica por vez, mantendo esse estado rápido na memória sem perder a durabilidade em disco. No Cloudflare, isso funciona porque há uma malha de roteamento proprietária encaminhando conexões para isolados V8 específicos, coordenando gravação em storage distribuído com baixa latência.

Quando você remove esse runtime dedicado e tenta fazer a mesma coisa dentro do seu monólito conectado ao Postgres, a mecânica muda por completo. Uma biblioteca precisa resolver três desafios usando apenas SQL:

  1. Garantir que apenas um processo no cluster execute o objeto com aquele identificador em um dado instante.
  2. Manter a fila de mensagens e eventos endereçados àquele objeto ordenada e consistente.
  3. Persistir o estado atualizado sem travar outros processos concorrentes.

Para resolver a exclusividade sem rodar um coordenador externo como Zookeeper ou etcd, a saída padrão no PostgreSQL é recorrer a travas consultivas (advisory locks) ou travas de linha com comandos de seleção exclusiva. Em SQLite local ou em ambientes de teste, isso parece mágico. Você importa a biblioteca, define uma classe, processa mensagens sequencialmente e tem persistência imediata. O problema começa quando você joga tráfego real de produção em cima de uma instância PostgreSQL compartilhada.

O gargalo das conexões e a disputa por locks

O primeiro ponto onde esse desenho encontra atrito sério é no gerenciamento de conexões. O PostgreSQL trabalha com processos dedicados por conexão (ou threads em versões em desenvolvimento). Se a biblioteca mantém uma conexão aberta para segurar uma trava consultiva enquanto um objeto durável processa uma sequência de ações ou aguarda eventos, o pool de conexões do PgBouncer entra em exaustão muito rápido.

Mesmo se a implementação adotar um modelo de lease temporário (adquirindo a trava, atualizando um timestamp na tabela e liberando a conexão logo em seguida), cria-se uma sobrecarga contínua de escrita apenas para renovar a posse do objeto. Em sistemas que operam dezenas de milhares de entidades ativas ao mesmo tempo, como sessões ativas de usuários, canais de comunicação ou agentes em execução, essa rotatividade de transações curtas de coordenação compete diretamente com as consultas transacionais vitais do seu produto.

A amplificação de escrita no modelo MVCC

Outro detalhe que o entusiasmo inicial costuma ignorar é a arquitetura de concorrência multiversão (MVCC) do PostgreSQL. Cada vez que um ator recebe uma mensagem rápida, altera seu estado interno e grava essa mudança em uma linha da tabela, o Postgres não sobrescreve os bytes antigos. Ele escreve uma tupla inteira nova no disco e marca a anterior como morta.

Se um Durable Object receber cem atualizações de estado por minuto, são cem novas tuplas por minuto para uma única entidade. Multiplique isso por centenas de entidades e você terá uma geração massiva de registros mortos (bloat). Se a tabela tiver índices complexos ou se as atualizações não se qualificarem para otimizações de atualização na mesma página (HOT updates), o autovacuum começará a consumir CPU e I/O de disco de forma agressiva para limpar páginas antigas.

No Cloudflare Workers, o storage local do objeto foi desenhado especificamente para esse padrão de chave-valor transacional e isolado. No PostgreSQL, você está usando um motor relacional de uso geral para simular memória persistente orientada a atores. Funciona, mas cobra seu pedágio em escrita de log de transação (WAL) e manutenção de tabelas.

Onde a abordagem faz sentido na prática

Apesar desses limites técnicos evidentes, não acho que projetos como o SolidObjects devam ser descartados. Pelo contrário: para uma faixa considerável de problemas, a simplificação da arquitetura compensa os limites de escala.

O erro está em achar que Durable Objects em Postgres servem para telemetria de alta frequência ou jogos multijogador em tempo real com centenas de ações por segundo. Não servem. Mas eles são muito adequados para:

  • Orquestração de fluxos de trabalho que duram minutos ou horas, onde cada etapa precisa de garantia de execução única e recuperação após reinicialização do servidor.
  • Moderação e salas de atendimento interativo, onde a frequência de mensagens é ditada pelo ritmo humano e a simplicidade de não gerenciar um cluster Temporal ou Akka economiza semanas de trabalho de equipe.
  • Máquinas de estado para aprovação financeira ou processamento de pedidos, onde a consistência estrita e a transacionalidade com o restante dos dados relacionais superam qualquer exigência de latência de microssegundos.

Nesses cenários, a comodidade de manter tudo dentro do banco de dados relacional existente, aproveitando a mesma política de backup, réplicas e monitoramento, é uma vitória operacional palpável. Você troca a sobrecarga de gerenciar mais um cluster de infraestrutura pela sobrecarga de monitorar o consumo de conexões e o bloat de algumas tabelas específicas.

Convivendo com os trade-offs

A migração de padrões tradicionalmente distribuídos para dentro do banco de dados relacional já se provou viável em outras frentes. O Solid Queue e o Solid Cache mostraram no ecossistema Rails que muitas equipes estavam adicionando Redis apenas por inércia cultural, e não por necessidade real de vazão.

Com Durable Objects, o princípio de simplificação é parecido, mas a margem de tolerância do banco é mais estreita. Atores com estado em memória e persistência contínua exigem respeito estrito ao ciclo de vida das conexões e ao volume de escritas por segundo. Adotar essa abordagem para fugir de faturas imprevisíveis de nuvem serverless é uma decisão de engenharia muito legítima. Só não espere que o PostgreSQL se comporte como memória RAM distribuída infinita sem cobrar o preço em I/O e contenção de travas.

Fontes

PD

Sobre Pedro Dalben

CTO e arquiteto full stack especializado em Ruby on Rails, Vue.js, sistemas multi-tenant e infraestrutura distribuída.