- Implementar Durable Execution não exige necessariamente uma infraestrutura complexa à parte; o ponto central é preservar com segurança o estado do workflow
- SQLite fornece estado persistente baseado em transações sem precisar de um serviço de banco de dados separado, mantendo com segurança o progresso do workflow sem hops de rede nem plano de controle adicional
- Usando Litestream para transmitir de forma assíncrona as alterações do SQLite para um storage de objetos compatível com S3, é possível manter o estado próximo ao runtime e ainda copiá-lo para fins de backup, migração e inspeção
- Em workflows bursty e experimentais como agentes de IA, uma configuração de micro-VMs ou contêineres em que cada agente tem sua própria pequena unidade de estado em SQLite é mais simples, mais barata e melhor para isolamento de falhas do que um único sistema grande e compartilhado
- Quando há necessidade de alta disponibilidade ou ampla escalabilidade compartilhada, o Postgres é mais adequado, mas muitos sistemas de workflow não precisam desse nível de infraestrutura no início
O núcleo da execução persistente
- A execução persistente costuma ser discutida como se exigisse infraestrutura persistente, mas o que realmente importa é o estado do workflow (workflow state), enquanto a computação pode continuar sendo barata e descartável
- No Obelisk, o progresso do workflow é armazenado em um execution log, reproduzido a partir do histórico persistido, e as atividades podem ser tentadas novamente
Por que o SQLite é adequado
- O SQLite oferece estado persistente baseado em transações sem adicionar um serviço de banco de dados separado
- É possível manter com segurança o progresso do workflow sem hops de rede, plano de controle adicional ou nova carga operacional
- Em muitos sistemas, um arquivo de banco de dados local é exatamente o nível certo de infraestrutura
Garantindo portabilidade com Litestream
- O Litestream transmite de forma assíncrona as alterações do SQLite para um storage de objetos compatível com S3, mantendo o estado próximo ao runtime e permitindo cópias para backup, migração e inspeção
- Atenção: como a replicação do Litestream é assíncrona, as gravações locais mais recentes que ainda não tiverem sido copiadas antes de o volume do SQLite desaparecer podem ficar ausentes na restauração
- Para muitos workflows de IA e experimentais, esse nível é aceitável, mas é um modelo diferente de um banco de dados compartilhado com alta disponibilidade
- Modelo operacional prático: executar o servidor Obelisk junto com o banco SQLite, fazer backup com Litestream e, quando necessário, um observador faz pull do banco — o mesmo arquivo pode ser reutilizado para replay local, debugging e análise do comportamento do agente
Por que isso é especialmente vantajoso para agentes de IA
- Agentes de IA e workflows gerados por IA têm características bursty e experimentais, e faz sentido trabalhar com pequenas unidades de estado autocontidas por agente ou por tenant
- Uma configuração com uma pequena frota de servidores baseada em micro-VMs ou contêineres, cada um operando um banco SQLite independente com backup em storage de objetos, é mais simples, mais barata e melhor em isolamento de falhas do que um único sistema grande, compartilhado e sempre ativo
Quando usar Postgres
- O Obelisk também oferece suporte a Postgres, que deve ser escolhido quando forem necessárias alta disponibilidade, ampla escalabilidade compartilhada ou características de implantação em que um banco de dados em rede seja mais apropriado
- O Postgres também é mais adequado quando a replicação assíncrona para storage de objetos não corresponde ao modelo de durabilidade desejado
- Muitos sistemas de workflow não precisam começar, já na fase inicial, com uma infraestrutura acima do que o estado realmente exige
- Com apenas SQLite local + backup S3 via Litestream + workers baratos, já é possível montar um sistema persistente com infraestrutura mínima, e isso pode ser o padrão mais sensato em ambientes com agentes de IA
1 comentários
Comentários do Hacker News
Comecei a montar workflows com Temporal, mas para apps locais ele é distribuído de forma bem leve e, em instalações locais isoladas, usa SQLite
O tratamento de retries de API e a organização de workflows e tarefas ficam realmente muito simples, então recomendo experimentar. Filosoficamente, vai exatamente na mesma direção proposta por este texto, mas acrescenta uma interface muito rica e flexível, ótima para agentes. Também é fácil inspecionar workflows pela UI web e revisar execuções de agentes
O Temporal coloca uma confiabilidade muito maior no sistema quase de graça. Como sistemas distribuídos e confiáveis são difíceis, acho melhor não reinventar a roda
Se você quer conseguir inspecionar facilmente o banco SQLite, entender o que está acontecendo no workflow, compor tarefas individuais e tornar simples a chamada de workflows, vale a pena olhar o Temporal
Junto com isso, quase deixei de usar arquivos para agentes. Markdown e JSON são bons, mas ao criar pequenos apps locais acabam parecendo armadilhas. LLMs lidam bem com SQLite e, a partir dele, dá para renderizar em Markdown, JSON ou no formato que você quiser. Se o agente puder consultar apenas linhas específicas, em vez de abrir o jq ou fazer grep em Markdown, também economiza muitos tokens. Você acaba tendo um sistema de gestão de dados portátil e autocontido que força uma estrutura de dados mais disciplinada do que vários arquivos. Se um pequeno projeto local crescer ou se formalizar mais, ele também pode evoluir para MySQL/Postgres, e você já terá esquema e disciplina de dados
O Temporal fica bem mais complexo quando cresce. Operar Cassandra não é divertido, e Ringpop e TChannel são difíceis de depurar quando há problemas. O suporte a backend SQL, por causa das exigências de consistência, não oferece réplicas com escalonamento horizontal e só permite instância única
Dependendo de como o código é escrito, também fica complicado modificar o código embutido no workflow. Mudanças que alteram a ordem dos eventos no histórico quebram o determinismo dos workers já implantados
Usamos bastante Temporal, e quem começou com scripts ou automação simples adora, mas quem construiu sistemas reais de produção em cima dele odeia. Pode ser falta de maturidade operacional da nossa parte, mas o quadro cor-de-rosa que aparece nestes comentários não bateu com a minha experiência
Nunca fiz isso na prática, mas queria ouvir mais experiências reais
Não entendo a obsessão em usar SQLite em apps reais de produção. SQLite é um banco de dados embarcado, então não é nada adequado para gerenciamento de concorrência
É para esse tipo de trabalho que existem servidores de banco de dados como Postgres e MySQL. O papel deles, como um todo, é permitir que vários processos modifiquem dados ao mesmo tempo a partir de máquinas diferentes
Este é um princípio básico da ciência da computação, e o pessoal que grita “SQLite para tudo” parece ter um pouco de falta de experiência
SQLite é um excelente banco de dados de produção para muitas cargas de trabalho reais, e isso está amplamente documentado. Como ele é muito diferente do Postgres, é preciso aprender uma tecnologia completamente diferente
Uma forma de ver isso é que SQLite pode se encaixar bem em partes de um sistema que acabam tendo uma partição forte de forma natural
net/httpdo Go, muitas vezes já consegue lidar com toda a carga imaginável de algum serviço. Mais ainda se for possível aumentar o hardware com o tempo, e o SQLite pode escalar com facilidade até centenas de milhares de TPSO que realmente se abre mão é de alta disponibilidade/failover e recuperação de desastres, mas também existem soluções para isso. Sistemas de servidor único em geral são surpreendentemente robustos. Isso porque, sem um plano de controle complexo, muitas vezes a disponibilidade cai à medida que o sistema cresce
Gosto de reavaliar as “boas práticas” existentes à luz das mudanças tecnológicas. Ainda mais quando isso vai na direção de aumentar a simplicidade. Rodar um site de mídia social para a família em um único banco SQLite num VPS é ótimo. São cerca de 15 usuários e quase não há manutenção. Também rodo uma instância do FreshRSS e uma página “now” com SQLite
No trabalho também usei SQLite para todo tipo de coisa nas últimas décadas. Usei para filas de trabalho temporárias, para carregar e consultar rapidamente muitos logs localmente, e para exibir e filtrar em tempo real com o excelente https://github.com/simonw/datasette do simonw
Em vez de “SQLite para tudo”, diria que é mais algo como “SQLite em muito mais lugares do que você imagina”
O trabalho de kentonv/Cloudflare com SQLite na edge pode ter popularizado um pouco mais essa ideia, mas isso já era uma tendência. https://blog.cloudflare.com/sqlite-in-durable-objects/
Conhecer e querer aproveitar esses casos pequenos e úteis não é falta de experiência; pode até ser um sinal de experiência
É bem possível que SQLite seja mais usado do que todos os outros mecanismos de banco de dados somados. Existem bilhões de cópias do SQLite em uso no mundo real. Ele está presente em dispositivos Android, iPhones e aparelhos com iOS, Macs, instalações do Windows 10/11, Firefox/Chrome/Safari, Skype, iTunes, cliente do Dropbox, TurboTax e QuickBooks, PHP e Python, na maioria das TVs e set-top boxes, na maioria dos sistemas multimídia automotivos e em incontáveis aplicações
https://sqlite.org/mostdeployed.html
Isso torna a escalabilidade muito mais fácil de entender. É só dividir e repetir, depois dividir e repetir de novo. Algo como adicionar mais um shard a cada N usuários
Em troca, você passa a ter outros problemas, como consultas entre shards — por exemplo, para análise — e como nivelar a carga quando usuários abandonam o sistema ou envelhecem
Mas dá para evitar todo o problema de escalonamento de índices compartilhados causado por inserções/atualizações em uma base massiva de usuários
Acaba virando mais um banco de dados hierárquico do que um banco de dados relacional
Substituí tudo isso por Go + SQLite: Intercom, Zendesk, marketing por e-mail, Kanban, Todo, stack de pagamentos, issue tracker, fórum, monitoramento de uptime, clone do PagerDuty
Como vendo dezenas de produtos, pensei: e se eu mesmo construísse tudo?
Tudo roda no mesmo servidor e usa pouquíssima memória. Substituí todas as ferramentas SaaS que usava por isso
Ao migrar para um servidor dedicado, reduzi para cerca de 1/10 o que pagava por soluções gerenciadas em nuvem, mantive a mesma alta disponibilidade e ainda consegui latência menor. Parte do motivo também foi o aumento da latência de cauda por causa de noisy neighbors em VPS
Antes eu gastava muito dinheiro com essas coisas, mas agora estou há 4 meses operando assim e só precisei de pequenas atualizações
O deploy é realmente simples. Nada de Docker nem Kubernetes, só serviços
systemde binários compilados na máquina de desenvolvimento e enviados para deployEu também pagava por serviços como MaxMind e IPData, mas criei meu próprio serviço de geolocalização por IP, e nos testes ele teve desempenho melhor do que a maioria das soluções existentes
Começou como um substituto para o Uptime Robot, depois ganhei confiança e substituí o PagerDuty. Depois disso, substituí o Intercom
Por fim, eu sempre ouvia que “não se deve construir a própria stack de pagamentos”, mas pensei YOLO e decidi cometer esse erro por conta própria. Estudei as soluções de pagamento existentes, desenvolvi e implantei a minha, e até agora não houve nenhum problema
Na frente uso o Caddy
Percebi que, das funcionalidades que a maioria dos produtos SaaS oferece, eu de fato só usava 1% a 5%, e que as funções de que eu realmente precisava ficavam cada vez mais enterradas dentro dessas plataformas “enterprise-grade”, dificultando o workflow
Não vou mostrar os produtos comerciais, porque parceiros e clientes provavelmente não gostariam de ver o quão barato eu sou, mas eu chamo isso de ser engenhoso
Posso mostrar um app gratuito. Lancei recentemente e ele já tem mais de 20 mil usuários: https://macrocodex.app/
Esse app usa apenas o clone do Zendesk. O e-mail é tratado com roteamento da Cloudflare, então o custo operacional é quase zero
Há um grande salto entre um arquivo e um banco de dados com múltiplas partições. Quando há operação real em jogo, rodar o banco em container não faz meu estilo
Pessoalmente, muita coisa de ETL pode ser processada localmente sem trazer um banco de dados enterprise para a equação. Nesses casos, o DuckDB é de 5 a 10 vezes melhor que o SQLite, e muito mais simples e rápido do que subir um banco Postgres dedicado
Em scripting geral, não tem nem comparação entre um script awk de 20 linhas e um script SQL equivalente com DuckDB, muito mais limpo, robusto e fácil de manter
Espero que a MotherDuck não esteja numa situação em que precise inflar e despejar para fazer IPO. Seria triste perder essa ferramenta por causa da ganância corporativa de sempre
Achei divertida a parte do script awk de 20 linhas. Ontem mesmo no Ubuntu Summit eu defendi quase exatamente esse ponto. A partir de certo momento, escrever shell scripts com GNU coreutils deixa de ser realista, e scripts SQL com DuckDB escalam muito melhor em complexidade e manutenção, e muitas vezes também em desempenho. Os slides estão aqui: https://blobs.duckdb.org/slides/duckdb-ubuntu-summit-2026.pd... páginas 32 a 36
Além disso, a MotherDuck desenvolve um DBaaS de código fechado sobre o DuckDB. Ela é construída em cima do DuckDB, e você se conecta à MotherDuck usando DuckDB, mas é uma empresa separada, com investimento de VC e sede em Seattle
O DuckDB é desenvolvido pela DuckLabs, uma empresa bootstrap baseada em receita, de Amsterdã. A propriedade intelectual do projeto pertence a uma terceira organização, a DuckDB Foundation, uma entidade sem fins lucrativos da Holanda. Para mais detalhes, veja https://duckdb.org/faq#how-are-duckdb-the-duckdb-foundation-...
Criei uma biblioteca que permite fazer atualizações concorrentes com segurança em um banco SQLite no S3[0]
Ela usa a pouco conhecida extensão de sessions do SQLite e compare-and-swap do S3 em um pequeno arquivo de metadados para funcionar de forma bem eficiente e segura. Tenho usado isso com satisfação em vários projetos pequenos nos quais preciso de um banco com estado em funções Lambda, mas não quero pagar o custo de uma instância completa de banco de dados
[0]: https://github.com/psanford/s3db
O SQLite tem um desempenho surpreendentemente bom em aplicações de nó único, mesmo comparado ao Postgres
O Postgres usa muito mais memória, e a E/S precisa passar por comunicação entre processos. Já o SQLite consegue manter tudo dentro do processo por meio de um pool de conexões compartilhado
Estou testando vários mecanismos de armazenamento para harnesses de agentes, e com SQLite consegui até 7,5 mil sessões simultâneas em uma única vCPU, enquanto o Postgres travava ou esgotava as conexões
[0] https://github.com/impalasys/talon/pull/23#issuecomment-4577...
No momento em que você sai da thread atual, já está perdendo em termos de latência. Se não obrigar comunicação entre threads, o SQLite pode operar em escala de microssegundos
No contexto de nó único, o Postgres é excesso. Não se deveria esperar que ele competisse com o SQLite
É quase como fazer benchmark entre um HashMap em memória e o Redis, e se surpreender porque o HashMap vai bem em condições ideais
Depois de ler sobre o SQLite por anos, experimentei usá-lo em um projeto pessoal e, vindo do Postgres, fiquei chocado com o quão fraco é o sistema de tipos
É realmente inferior, e não entendo por que recebe tantos elogios
https://sqlite.org/datatype3.html
https://www.postgresql.org/docs/current/datatype.html
Lidar com data/hora parece usar um banco de dados de 30 anos atrás, e nada é realmente imposto na inserção. Alguém precisa me explicar por que tanta gente gosta disso
PRAGMA journal_mode = WAL
PRAGMA foreign_keys = ON
Something non-null
PRAGMA busy_timeout = 1000This is fine for most applications, but see the manual
PRAGMA synchronous = NORMALIf you use it as a file format
PRAGMA trusted_schema = OFFDependendo do binding, opções adicionais podem ser necessárias. Por exemplo, aplicações Python não deveriam usar os padrões do módulo sqlite3. Esses padrões simplesmente estão errados. Antes do 3.12, nem havia alternativa além de usar um binding fora da biblioteca padrão: https://docs.python.org/3/library/sqlite3.html#transaction-c...
Você também deveria usar tabelas strict. https://www.sqlite.org/stricttables.html
Embora a ergonomia seja ruim, também dá para usar restrições CHECK. Por exemplo, é possível com o suporte embutido a datas do SQLite, mas é meio esquisito:
CHECK (
date(my_date_col) IS NOT NULL
AND my_date_col = date(my_date_col)
)
O IS NOT NULL é necessário porque
dateretorna NULL para datas inválidas. A outra verificação é necessária porque ela também aceita dias julianos; por exemplo,date('2026')vira algum momento no ano 4707 a.C.Concordo que, especialmente antes das tabelas strict, isso era decepcionante
Vale a pena olhar para o DuckDB. É próximo de um SQLite com tipagem de verdade. Mas, em vez de OLTP — isto é, array de structs — ele é OLAP, isto é, struct de arrays, então pode ter desempenho pior em cargas típicas de SQLite. Na prática, para uma aplicação que realmente consideraria um ou outro, imagino que a diferença não seja grande
Depois de usar vários clusters grandes de Postgres, migrei para SQLite, e um serviço com MAU de 7 dígitos é sustentado inteiramente por durable objects com SQLite
É preciso pensar o padrão de acesso de outra forma, mas os benefícios valeram muito a pena
É um bom enquadramento. Se o problema principal é armazenar o estado do workflow de forma durável, com possibilidade de inspeção e recuperação fácil, então muitas vezes o SQLite já basta
Quero muito ver a próxima iteração dessa ideia virar “para workflows duráveis, basta um log”
Um motivo pelo qual soluções no estilo “basta um log” podem falhar é quando logs não confiáveis viram um ataque por injeção[1]
Confira o SBOM, e não se esqueça de incluir também o pipeline de CI/CD[2]
[1] https://news.ycombinator.com/item?id=48315440
[2] https://github.com/jqwik-team/jqwik/issues/708#issuecomment-...
Falando sério, ser especialista é usar a ferramenta certa para o trabalho