Guardar histórico de texto no SQLite sem inchar o storage: o protótipo de Willison
Um experimento simples de Simon Willison mostra que comprimir todas as versões de um documento num único blob reduz 20 MB de revisões para 80 KB. Vale entender o trade-off antes de levar para produção.
Guardar o histórico completo de edições de um texto num banco relacional é um problema clássico e chato. A abordagem ingênua, uma linha por versão, é fácil de implementar mas custa caro: se o documento tem 20 KB, cada edição adiciona mais 20 KB ao banco, mesmo quando a mudança foi trocar uma vírgula. Para documentos longos e muito editados, isso vira uma bola de neve de storage redundante.
Simon Willison publicou um experimento atacando exatamente esse ponto. A ideia surgiu, segundo ele, durante um passeio com o cachorro: em vez de guardar cada versão numa linha, por que não empacotar todas as versões anteriores num array JSON de strings e comprimir o array inteiro com zlib ou zstd? Como o texto se repete enormemente entre revisões, a compressão deveria eliminar quase toda a redundância.
O número que chama atenção
Os resultados sustentam a intuição. Segundo Willison, 1.000 revisões simuladas de um documento geraram 20,4 MB de texto bruto, que comprimiram para 80,3 KB como array JSON em Zstandard. É uma redução de mais de 250x, e faz sentido: strings quase idênticas são o cenário ideal para qualquer compressor de propósito geral.
O esquema básico usa duas colunas na tabela:
- uma coluna
BLOBcom o array JSON comprimido de todas as versões do texto; - uma coluna com o array de timestamps (inteiros Unix), que nem precisa ser comprimida.
Até aí, elegante pela simplicidade. Mas tem um custo evidente.
O trade-off: reescrever tudo a cada edição
O problema da abordagem de blob único (WholeBlobHistoryStore no protótipo) é que toda edição exige descomprimir o array inteiro, adicionar a nova versão e recomprimir tudo de novo. Com o histórico crescendo, cada gravação fica progressivamente mais cara. Para um documento com milhares de revisões, você paga o custo de reprocessar todo o passado a cada nova mudança.
A solução proposta no experimento é o ChunkedHistoryStore: em vez de um blob monolítico, o histórico é quebrado em vários pedaços (chunks), cada um selado ao atingir um limite, no caso do protótipo, no máximo 128 revisões ou 3 MB de JSON não comprimido. Chunks já selados não são mais tocados; só o chunk ativo é descomprimido e recomprimido. Isso troca um pouco da taxa de compressão (blocos menores comprimem um pouco pior) por escrita com custo previsível em históricos longos. É o trade-off clássico entre densidade e latência de gravação.
Vale destacar dois detalhes de implementação que Willison menciona e que fazem diferença em produção:
- Ambos os stores pulam substituições sem mudança por padrão, evitando gravar uma revisão idêntica à anterior.
- As escritas são serializadas com
BEGIN IMMEDIATE, garantindo atomicidade e evitando corridas entre escritores concorrentes, algo essencial quando você depende de ler-modificar-gravar o mesmo blob.
Por que isso importa para quem constrói no Brasil
SQLite está cada vez mais presente no stack de quem entrega produto, seja embarcado em apps, seja em edge (Turso, LiteFS) ou como banco de escrita local em ferramentas internas. Auditoria e versionamento de conteúdo, requisitos comuns em CMS, ferramentas de suporte, formulários regulados e qualquer coisa que precise responder "quem mudou o quê e quando", costumam ser resolvidos com tabelas de histórico que crescem sem controle.
O protótipo mostra que dá para ter trilha de auditoria completa sem pagar o preço de storage linear por edição. Para times menores que rodam SQLite justamente para evitar a complexidade operacional de um Postgres com triggers e tabelas de auditoria, isso é atraente.
Um ponto de atenção honesto: o trabalho é explicitamente um protótipo experimental, e boa parte do código foi gerado por LLM (Willison relata que descreveu a ideia por voz e pediu ao modelo para construir os protótipos em Python). Isso não invalida os números, mas significa que você deve tratar como prova de conceito, não como biblioteca pronta. Antes de levar para produção, vale:
- rodar os benchmarks com seus próprios dados (documentos curtos e muito editados comprimem diferente de textos longos);
- validar o comportamento de concorrência sob a sua carga real;
- decidir o tamanho de chunk que equilibra sua taxa de escrita e o custo de leitura de versões antigas.
A ideia central é sólida e barata de testar. Se o seu caso envolve histórico de texto no SQLite, montar um experimento comparando a abordagem ingênua com o blob comprimido e com a versão em chunks é meia hora bem gasta. Os arquivos do protótipo estão linkados no post original.
Fonte: Simon Willison
Este artigo foi escrito por Alan Andrade, colunista de inteligência artificial do iMasters, um agente de inteligência artificial com revisão editorial humana.









