Bug de 16 anos no SQLite causou 19 corrupções de banco na Tailscale
Em seis meses, a Tailscale enfrentou 19 incidentes de corrupção rastreados até uma condição de corrida entre checkpoint e escrita no WAL, presente no SQLite havia mais de uma década.

A Tailscale publicou um relato técnico detalhando como ajudou a encontrar e corrigir um bug com pelo menos 16 anos no SQLite, batizado pelos mantenedores de "WAL-Reset bug". Segundo o post no blog da empresa, a falha causou 19 incidentes separados de corrupção de banco ao longo de seis meses, entre agosto do ano passado e o começo deste ano, derrubando a estabilidade do control plane do serviço.
A arquitetura em jogo
A Tailscale usa SQLite como banco primário desde 2022. O control plane é dividido em "shards", e cada shard tem um banco SQLite acessado exclusivamente por um único processo Go, seguindo o design single-writer para o qual o SQLite foi feito. O pipeline de backup tirava um snapshot completo do arquivo a cada poucos minutos e subia para um bucket S3, rodando sem incidentes desde o início de 2023.
Em agosto passado, um pipeline que lia esses backups reportou erro. Rodando PRAGMA integrity_check, a equipe confirmou a corrupção. Repararam o banco, investigaram e não acharam causa. O problema voltou repetidas vezes.
Por que era difícil de reproduzir
O bug resistiu às investigações iniciais. Não havia mudanças recentes relevantes no código de baixo nível que fala com o SQLite, escrito anos antes. Não havia fator comum entre incidentes: não estava atrelado a shard, cliente, feature, horário ou carga. Sem condição de gatilho confiável, era impossível reproduzir sinteticamente. A equipe teve que recorrer a telemetria forense passiva no ambiente de produção para pegar a corrupção em flagrante.
A irregularidade agravava o problema: às vezes os incidentes ficavam horas de distância, às vezes semanas. Houve um período de seis semanas sem corrupção antes de os incidentes voltarem no Natal.
Diante da dificuldade, a Tailscale fechou um contrato de suporte profissional com os desenvolvedores do SQLite, o que deu acesso direto à expertise do time central. Juntos, mapearam várias teorias, como locks POSIX quebrados no close(), má gestão de memória do SQLite ou uso multithread com thread safety desabilitada, descartando cada uma após novos incidentes.
A pista no log de transações
Para restaurar serviço sem rollback total, a equipe construiu um pipeline que registrava cada statement SQL modificando o banco. Como o SQLite é single-writer com transações serializáveis, o histórico era linear e determinístico, algo que não valeria para Postgres ou MySQL multi-writer.
Em dois incidentes, os logs não reaplicaram limpo: dados escritos e commitados por uma transação estavam invisíveis para transações posteriores. Uma escrita sumiu sem erro, o que deveria ser impossível.
O bug no checkpoint
A suspeita convergiu para o processo de checkpoint. A Tailscale roda SQLite com Write-Ahead Logging e assume controle manual do checkpoint para fazer backups rápidos e consistentes, rodando de forma agressiva. As métricas mostravam o SQLite reportando cópia de mais páginas do WAL do que existiam.
Os desenvolvedores do SQLite criaram um shim de tracing sobre o virtual filesystem, chamado tmstmpvfs, com código no repositório público. Após o incidente seguinte, os logs revelaram a causa: um data race raro entre um checkpoint e uma transação de escrita. Se a escrita ocorre em um momento específico do checkpoint, o processo se confunde, acha que copiou páginas do WAL para o arquivo principal quando não copiou, e esses dados se perdem permanentemente. O bug era tão raro que os próprios mantenedores precisaram adicionar código para dispará-lo deliberadamente em testes.
A correção saiu no SQLite 3.52.0. Mas a versão trouxe um segundo problema: uma otimização mudou sutilmente o arredondamento em conversões texto-para-float, gerando falsos alarmes de corrupção em índices de expressão sobre timestamps. Os mantenedores retiraram o 3.52.0 e publicaram o 3.51.3, apenas com a correção do WAL-Reset. A Tailscale reduziu a precisão dos timestamps para segundos inteiros; o SQLite 3.53.0 trouxe índices de expressão auto-corretivos.
O que fica de lição
A própria Tailscale resume o aprendizado: rodar "boring technology" de forma não padrão é um risco. Tudo que faziam era configuração pública, documentada e suportada, mas o controle manual do checkpoint em ritmo agressivo os tirou do caminho batido, onde reside a maior parte dos testes e da confiabilidade.
Para quem constrói software no Brasil e adota SQLite em produção, cada vez mais comum com o padrão single-writer em serviços enxutos, o recado prático é direto: atualize para 3.51.3 ou posterior, e desconfie de configurações fora do fluxo padrão, por mais madura que a dependência pareça.
Fonte: Hacker News
Este artigo foi escrito por Redação iMasters, um agente de inteligência artificial com revisão editorial humana.







