Executando verificação de segurança...
30

Chega de briga, pessoal! Seu SQLite pode ser até 10x mais rápido (e não é mágica!) ✨🚀

E aí, galera do TabNews! Tudo beleza?

Quem nunca deu uma passadinha por aqui e se deparou com aquele post "Devo ou não adotar o SQLite?" 🥊 ou "SQLite é rápido o suficiente pra minha aplicação?" 🤔. A gente sabe que o SQLite é um banco de dados incrível para projetos que precisam de simplicidade e portabilidade, mas a performance pode ser um ponto de interrogação pra muita gente. A boa notícia é que a resposta para a lentidão pode estar na sua cara o tempo todo, mas ninguém te contou a dica de ouro! 🤫

Recentemente, eu estava assistindo um vídeo no YouTube que me abriu os olhos (e me fez rir de nervoso por não saber disso antes). A dica é simples e pode turbinar seu SQLite em até 10 vezes! 🏎️💨 E não, não é bruxaria, nem feitiço, é pura otimização! A chave para essa transformação está em uma configuração simples que você provavelmente nunca prestou atenção: o modo de journal.

O dilema do Rollback vs. a magia do WAL

A maioria de nós, desenvolvedores que usam SQLite, opera com o modo de journal padrão, que é o Rollback (também conhecido como DELETE ou TRUNCATE). E é aí que mora o perigo da lentidão! 😱

Pense no Rollback como aquele seu amigo super precavido que, antes de te devolver a grana que pegou emprestada, primeiro faz uma cópia da sua nota de R$ 100 para um cofre (o arquivo de journal) para o caso de algo dar errado, e só depois te devolve a nota original. Se a transação falhar, ele usa a cópia do cofre para restaurar o estado original. O processo é seguro, atômico, mas tem um custo altíssimo de performance por causa da criação e deleção constante desse arquivo de journal.

Já o segredo para a velocidade se chama WAL (Write-Ahead Log). Esse cara é o oposto do Rollback: ele simplesmente anota o que vai mudar em um arquivo de log à parte (-wal) e, de tempos em tempos, passa a limpo no banco de dados principal em um processo chamado "checkpoint". ✍️📊

Imagina que é como se você estivesse em uma reunião super corrida, escrevendo todas as tarefas e ideias em um post-it (o WAL) sem parar. Só no final do dia, ou quando o post-it encher, você organiza tudo na sua agenda oficial. O resultado? Você consegue anotar mil coisas rapidamente e só se preocupa com a organização depois.

As 7 vantagens de ser amigo do WAL 😎

O WAL não é apenas uma alternativa; ele é uma evolução. A documentação oficial do SQLite e outros testes de performance detalham os motivos para ele ser tão superior:

  1. Velocidade Absurda: Em cenários com muitas escritas e leituras simultâneas, o WAL é significativamente mais rápido. Em um teste real de benchmark, o modo WAL alcançou 70.000 leituras/s e 3.600 escritas/s, enquanto o modo rollback ficou em apenas 5.600 leituras/s e 291 escritas/s. Isso é uma diferença de ordem de magnitude! 🤯

  2. Concorrência de Nível 1000: Com o WAL, os leitores não bloqueiam os escritores e vice-versa. Enquanto um escritor está ocupado no arquivo de log, múltiplos processos podem continuar lendo do banco de dados principal. Isso é essencial para aplicações com acesso concorrente.

  3. I/O Mais Inteligente: Sabe quando você está em uma fila de supermercado e o caixa está processando item por item, um após o outro? Isso é I/O sequencial. Agora, imagine se o caixa tivesse que, a cada item, correr para outro corredor, pegar um produto e depois voltar. Isso é I/O aleatório, muito mais lento e ineficiente. O WAL, por sua natureza, usa I/O sequencial. Ele simplesmente adiciona as novas transações no final do arquivo de log, uma atrás da outra. Essa abordagem é muito mais rápida para o disco, especialmente para SSDs, que são otimizados para esse tipo de operação.

  4. Menos fsync(): Sabe quando você está salvando um arquivo no seu computador e a tela "trava" por um instante? Isso acontece porque o sistema está forçando os dados que estão na memória a serem gravados no disco físico. Esse processo é o fsync(). O modo rollback tem que fazer isso a cada commit para garantir que a transação foi salva, o que é um processo lento. O WAL, por outro lado, faz menos fsync()s, pois as transações são primeiro adicionadas ao arquivo de log. Essa abordagem reduz drasticamente o tempo de espera do disco.

  5. Aumento na performance de 2x a 20x: Em aplicações com alto volume de escritas, o ganho de performance pode chegar a 20x. Isso acontece porque o WAL otimiza o fluxo de trabalho, permitindo que a escrita de dados no arquivo de log aconteça de forma muito mais rápida, sem a sobrecarga de bloqueios e movimentação de arquivos que o modo rollback enfrenta. A diferença pode ser sentida em cenários como a ingestão de dados em massa, a sincronização de informações ou qualquer aplicação que precise de muitas operações de escrita em um curto período.

  6. Otimização Persistente: Ao contrário do modo DELETE, que reverte para o modo padrão a cada fechamento de conexão, o WAL é um modo persistente. Ou seja, você o configura uma vez e ele continua ativo mesmo após a conexão ser fechada, garantindo que o seu aplicativo se beneficie da performance melhorada de forma consistente.

  7. Convivência em Paz: No mundo do SQLite, a busca por performance esbarra em um ponto crucial: a sincronização. O modo padrão, com synchronous=FULL, garante que, mesmo em caso de falha de energia, seus dados estarão intactos, mas a um custo altíssimo de lentidão. O WAL, por outro lado, foi projetado para conviver bem com uma opção mais relaxada, o synchronous=NORMAL. Essa configuração oferece um equilíbrio perfeito: ela ainda garante a integridade dos dados em caso de falhas da sua aplicação, mas sem a sobrecarga do FULL, resultando em uma velocidade muito maior. É a prova de que você pode ter segurança e performance na mesma frase.

Mas e a pegadinha? A vida não é só de flores (nem de WALs!) 😬

Nenhum herói vem sem um calcanhar de Aquiles, e com o WAL não é diferente. É crucial entender suas desvantagens para tomar a melhor decisão para o seu projeto:

  1. Compatibilidade de Rede Zero: O WAL exige um sistema de memória compartilhada e, por isso, não funciona em sistemas de arquivos de rede (NFS, por exemplo). Todos os processos que acessam o banco de dados precisam estar na mesma máquina.

  2. Durabilidade Parcial: A principal desvantagem, como o artigo de Eric Draken apontou, é que uma transação com synchronous=NORMAL no modo WAL pode, em teoria, reverter após uma queda de energia ou um crash do sistema operacional. ⚡️💥 Embora isso não aconteça em falhas da aplicação (que o WAL se protege muito bem!), o risco de uma falha de hardware é algo que precisa ser levado em conta. É como uma anotação em um post-it que você ainda não passou para a sua agenda oficial; se a agenda for para o ralo, a anotação pode se perder.

  3. Arquivos Extras: O WAL cria dois arquivos auxiliares semi-persistentes (-wal e -shm) que permanecem ao lado do arquivo do banco de dados principal. Para algumas aplicações que buscam a simplicidade de um único arquivo, isso pode ser indesejável.

  4. Limitações Técnicas: Ele não permite alterar o page_size enquanto o modo WAL estiver ativo e não garante atomicidade em transações que alteram múltiplos bancos de dados anexados. Mas por que isso é um problema? O page_size é como a "capacidade de uma página" no seu banco de dados. Se uma page_size for de 1KB, cada página só consegue armazenar 1KB de dados. Se você tem dados maiores que isso, o banco de dados precisa quebrar e espalhar essa informação em várias páginas, o que não é ideal. Ter a flexibilidade para ajustar a page_size permite que você otimize o banco de dados para o tipo de dados que você está armazenando, melhorando a performance geral. No WAL, essa flexibilidade é perdida.

Como colocar o WAL à prova (você mesmo!)

Cansado de teoria e pronto para ver os números voarem? Aqui está um processo simples e prático para ativar o WAL e fazer um benchmark rápido, só para você ver a mágica acontecer.

Passo 1: Ativando o WAL

Você pode ativar o modo WAL em qualquer banco de dados SQLite existente com um único comando. Basta abrir seu banco de dados em uma ferramenta como a interface de linha de comando sqlite3 e rodar esses comandos:

PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;

É isso! Se a resposta for wal e normal, a mudança foi bem-sucedida. O banco de dados agora permanecerá no modo WAL, mesmo depois que você fechar a conexão.

Passo 2: Um Benchmark Rápido

Para realmente apreciar a diferença, vamos fazer um teste simples. Usaremos o modo TRUNCATE (uma forma de rollback) como nosso ponto de partida.

Primeiro, faça uma cópia do seu arquivo de banco de dados para cada teste. Vamos chamá-los de meu_banco_truncate.db e meu_banco_wal.db.

Testando a Linha de Base (TRUNCATE Mode):

Abra o arquivo meu_banco_truncate.db.
Execute o seguinte comando para definir o modo de journal e, em seguida, fazer uma série de escritas rápidas.

PRAGMA journal_mode = TRUNCATE;
.timer on
BEGIN TRANSACTION;
-- Rode um monte de INSERTs ou UPDATEs aqui.
-- Ex: INSERT INTO minha_tabela (col1, col2) VALUES ('valor1', 'valor2');
-- Repita essa linha 1000 vezes ou use um script para isso.
END TRANSACTION;
.timer off

Anote o tempo que a operação levou para ser concluída.

Testando o WAL Mode:

Abra o arquivo meu_banco_wal.db.
Execute a mesma série de comandos, mas desta vez, ative o WAL.

PRAGMA journal_mode = WAL;
.timer on
BEGIN TRANSACTION;
-- Rode a mesma exata série de INSERTs ou UPDATEs do teste anterior.
END TRANSACTION;
.timer off

Você deve ver uma queda dramática no tempo de execução. Para resultados mais precisos, você pode aumentar o número de transações ou até mesmo usar um script simples em Python ou Node.js para automatizar o processo.

Este teste simples tornará a diferença tangível. Você poderá ver por si mesmo o quão mais rápidas suas operações de disco se tornam.

Então, qual é o veredito? ⚖️

A menos que sua aplicação tenha uma necessidade extrema de durabilidade em caso de falhas de energia (onde você talvez precise de synchronous=FULL ou um backup constante), ou que você esteja operando em um sistema de arquivos de rede, o WAL é a escolha mais inteligente para 99% das aplicações.

No entanto, o mundo do SQLite é cheio de nuances! Para operações de apenas inserção em SSDs, o modo TRUNCATE pode, em alguns casos, ter uma performance superior ao WAL. Mas, para a maioria dos cenários de uso real, que misturam leituras e escritas, o WAL é o rei da velocidade.

O modo WAL foi introduzido no SQLite desde a versão 3.7.0 (lançada em 2010!), e não há desculpa para não usá-lo. Mude o modo de journal para WAL! Ele é a maneira mais fácil e eficaz de turbinar seu banco de dados e resolver a maioria dos problemas de lentidão.

E aí, já sabia dessa? Deixe sua opinião aqui nos comentários. E que a força do WAL esteja com você! 🙏✨

Carregando publicação patrocinada...
8

Meus 2 cents,

Uma comparacao rapida:

Resumo de desempenho

RecursoConfiguração WAL + NORMALPadrão (DELETE + FULL)
Desempenho de escrita✅ Significativamente mais rápido devido a gravações sequenciais e menos sincronizações de disco por transação.❌ Mais lento devido ao bloqueio de todo o banco de dados e à realização de múltiplas gravações aleatórias em disco a cada commit.
Desempenho de leitura✅ Vários leitores podem operar simultaneamente com um único escritor, evitando bloqueios de leitura.❌ Uma transação de escrita bloqueia todo o banco de dados, impedindo todas as leituras e reduzindo a concorrência.
Concorrência✅ Alta. Suporta múltiplos leitores e um escritor operando ao mesmo tempo.❌ Baixa. Apenas um processo pode acessar o banco de dados por vez, tanto para leitura quanto para escrita.
Durabilidade dos dados⚠️ Alta resistência a falhas de aplicativos.
❌ Vulnerável à perda de dados em caso de queda de energia ou falha do sistema.
✅ Durabilidade máxima contra todos os tipos de falhas (aplicativo, sistema e perda de energia).
E/S de disco✅ E/S sequencial para a maioria das transações, o que é mais eficiente em hardware moderno.❌ E/S aleatória e fragmentada durante as transações, o que é menos eficiente.

Configuracoes extras (mais performance + mais riscos)

PRAGMA cache_size = -20000; (20 MB de cache em RAM, negativo mesmo - o padrao eh -2000 => 2Mb)

PRAGMA temp_store = MEMORY; (Armazena tabelas temporárias e índices em RAM.)

Configuracoes insanas

PRAGMA synchronous = OFF; (nao espera a escrita)
PRAGMA journal_mode = MEMORY; O journal (log de transações) fica só em RAM.

Riscos: em caso de crash da VM/queda de energia dados podem ser perdidos

No caso do insano, voce pode garantir a escrita a qualquer momento usando:

PRAGMA wal_checkpoint(FULL);

Isso forca o banco a escrever no disco;

Opcoes

PRAGMA wal_checkpoint(RESTART);
PRAGMA wal_checkpoint(TRUNCATE);

Na verdade voce pode forcar a escrita em qualquer modo, mas no caso insano eh particularmente interesante.

Saude e Sucesso !

1
1
1

gostei bastante dessas configurações insanas. com certeza adicionam muito à discussão e podem ajudar as pessoas que acessarem a publicação. muito obrigado pela ótima contribuição.

3

Meus 2 cents,

Um pouco mais sobre os comandos para forcar o flush do banco (via IA - mas dei uma revisada para ver se fazia sentido):

O que é um “checkpoint” no WAL

Em modo WAL, as escritas vão para o arquivo *.wal. O checkpoint copia as páginas confirmadas desse wal de volta para o arquivo principal .sqlite. Por padrão, o SQLite auto-checkpointa quando o wal atinge ~1000 páginas (≈4 MB dependendo do page size) ou quando a última conexão fecha o DB. Normalmente o checkpoint não trunca o wal; ele só “recicla” o arquivo para começar a sobrescrever do início (é mais rápido que apagar/criar de novo). (sqlite.org)

Diferenças entre FULL, RESTART e TRUNCATE

PRAGMA wal_checkpoint(FULL);

  • O que faz: espera até não haver escritor ativo e até que todos os leitores estejam na snapshot mais recente. Então move todas as frames do wal para o banco e faz fsync no arquivo principal.
  • Bloqueio: bloqueia novos escritores enquanto roda; leitores continuam (não são impedidos de iniciar).
  • Tamanho do WAL: pode continuar do mesmo tamanho (arquivo não é reduzido).
    (sqlite.org)

PRAGMA wal_checkpoint(RESTART);

  • O que faz: faz tudo que o FULL faz e depois espera até que todos os leitores passem a ler somente do arquivo principal (ninguém usando o wal).
  • Efeito extra: garante que o próximo escritorreiniciar o wal do começo (o cabeçalho é “resetado”).
  • Bloqueio: como no FULL, impede novos escritores enquanto está pendente; leitores não são impedidos, mas o comando espera os leitores atuais terminarem de usar o wal.
    (sqlite.org)

PRAGMA wal_checkpoint(TRUNCATE);

  • O que faz: igual ao RESTART mais um passo extra: trunca o arquivo wal para 0 bytes antes de retornar com sucesso (libera espaço em disco).
  • Bloqueio: idem ao RESTART.
  • Tamanho do WAL: vai a zero se o checkpoint completar.
    (sqlite.org)

Observação útil: o comando PRAGMA wal_checkpoint retorna uma linha com três inteiros:
busy, log, checkpointed. busy=1 indica que um FULL/RESTART/TRUNCATE não conseguiu completar (ex.: alguém segurava lock). (sqlite.org)

Como isso afeta sua aplicação e as configs já aplicadas

  • Concorrência/leitura:

    • FULL, RESTART e TRUNCATE bloqueiam novos escritores durante a operação (usam o writer lock) e podem invocar o busy handler; leitores seguem funcionando, mas o RESTART/TRUNCATE esperam leitores atuais saírem do wal para conseguir “reiniciar”/truncar. Planeje executar em janelas de menor tráfego de escrita. (sqlite.org)
  • Espaço em disco:

    • Somente TRUNCATE reduz fisicamente o tamanho do arquivo wal. FULL e RESTART deixam o arquivo como está (ele será reutilizado). Se você precisa liberar espaço imediatamente (após operações grandes ou um VACUUM), TRUNCATE é o certo. (sqlite.org)
  • Com WAL + synchronous=NORMAL:

    • É um bom equilíbrio de desempenho/segurança. Use FULL periodicamente se quiser “forçar” o flush para o .sqlite sem mexer no tamanho do wal. Use RESTART se o wal crescer porque há leitores de longa duração; ele garante que o próximo write recomece do início. Use TRUNCATE quando o objetivo for recuperar espaço. (sqlite.org)
  • Auto-checkpoint vs manual:

    • O auto-checkpoint (por padrão ou via PRAGMA wal_autocheckpoint) é PASSIVE – não bloqueia e pode deixar trabalho pendente se houver leitores/escritores. Os modos FULL/RESTART/TRUNCATE são “mais agressivos” e podem bloquear para concluir. (sqlite.org)
  • Tamanho máximo do WAL:

    • Se a preocupação é “não deixar o arquivo crescer demais”, combine com PRAGMA journal_size_limit (ex.: journal_size_limit=0 faz truncar/journals voltarem ao mínimo após ficarem inativos). Isso não substitui TRUNCATE, mas ajuda a manter o footprint. (SQLite)

Recomendações práticas

  • Rotina segura para produção (balanceada):

    -- manutenção periódica em horário de baixo tráfego
    PRAGMA wal_checkpoint(FULL);
    

    Garante que tudo no wal foi aplicado ao .sqlite, sem “pausas” tão longas quanto RESTART/TRUNCATE.

  • Quando o WAL está crescendo por leitores longos (ETLs, backups, conexões com long transactions):

    PRAGMA wal_checkpoint(RESTART);
    

    Faz um “limpa” lógico: o próximo writer começa no início do WAL, limitando crescimento.

  • Para liberar espaço em disco agora (pós grandes INSERT/DELETE/VACUUM, dispositivos com storage limitado):

    PRAGMA wal_checkpoint(TRUNCATE);
    

    Trunca o arquivo a 0 bytes (se conseguir os locks/leitores concluírem).

  • Dicas de operação:

    • Rode o checkpoint a partir de uma conexão de manutenção com busy_timeout configurado, para que FULL/RESTART/TRUNCATE possam esperar locks de forma amigável. Todos esses modos pegam um lock exclusivo de checkpoint e (para FULL/RESTART/TRUNCATE) também o lock de escrita. (sqlite.org)
    • Se a meta é limitar tamanho continuamente, combine auto-checkpoint com journal_size_limit e, ocasionalmente, um RESTART/TRUNCATE em janela de manutenção. (sqlite.org, SQLite)

Aqui vai um fluxo de decisão prático para escolher entre os modos de checkpoint (FULL, RESTART, TRUNCATE) dependendo da situação da sua aplicação:


🔄 Fluxo de decisão para wal_checkpoint

1. Seu objetivo é apenas garantir consistência (flush do WAL → arquivo principal)?

➡️ Use PRAGMA wal_checkpoint(FULL);

  • Copia tudo do WAL para o .sqlite.
  • Não reduz tamanho do arquivo.
  • Bloqueia apenas novos escritores enquanto roda.
    ✅ Melhor escolha para manutenção periódica (fim do dia, cron jobs).

2. O arquivo WAL está crescendo porque há leitores longos segurando páginas antigas?

➡️ Use PRAGMA wal_checkpoint(RESTART);

  • Faz o mesmo que o FULL.
  • Mas espera todos os leitores saírem do WAL.
  • Garante que o próximo writer começará do início do arquivo WAL.
    ✅ Ideal se você percebe que o WAL não diminui de tamanho mesmo após checkpoints comuns.

3. Precisa recuperar espaço em disco imediatamente?

➡️ Use PRAGMA wal_checkpoint(TRUNCATE);

  • Igual ao RESTART, mais truncamento físico do WAL para 0 bytes.
  • Útil após operações grandes (muitos INSERT/DELETE ou VACUUM).
    ⚠️ Pode ter pausa perceptível, pois espera leitores terminarem.
    ✅ Ideal em ambientes com limite de espaço em disco.

🧭 Resumo em tabela

SituaçãoMelhor checkpoint
Flush periódico de segurançaFULL
WAL crescendo por leitores longosRESTART
Preciso liberar espaço no discoTRUNCATE

⚡ Boas práticas extras

  • Configure PRAGMA wal_autocheckpoint (ex.: 1000 páginas ≈ 4 MB) para checkpoints automáticos leves (modo PASSIVE).
  • Use PRAGMA journal_size_limit para manter o WAL dentro de um teto (ex.: PRAGMA journal_size_limit=50MB;).
  • Agende FULL/RESTART/TRUNCATE em janelas de baixa atividade para evitar pausas perceptíveis.
2
1
3

Parabéns pelo artigo, vou colocar algumas questões extras.

O problema de só poder ter uma escrita é resolvido com um sistema de filas, que hoje tem softwares prontos para gerir para você. E a maioria das escritas serão muito mais rápidas do que se fossem concorrentes.

A questão de mudar o tamanho da página não costuma criar problemas, porque você pode fazer isso offline e é extremamente raro ser necessário, em geral as pessoas adotam 4KB ou 8KB e isso é o melhor para quase todo tipo de aplicação.

A questão de não ter atomicidade com DBs anexados é um problema em casos mais complexos e que o SQLite pode não ser a melhor opção, mas o problema pode acontecer em outros DBs também em certos cenários, até porque eles nem costumam ter o conceito de DBs anexados, ou seja, já é separado por complexo.

Se não me engano o modo sem WAL também não pode ser usado com acesso direto em rede.

Convenhamos, quase nenhuma aplicação hoje em dia faz acesso direto em rede, ele sempre passa por servidor de aplicação que está na mesma máquina, e esse servidor pode ser até o seu servidor HTTP + sua aplicação, ou seja, para web isso nunca foi problema, e para outras formas depende de você escolher ter um servidor de aplicação, que pode ser sua própria aplicação.

Como ele é mais simples ele dá conta de cenários pesados mais fácil que outros SGBDs, mas tem casos específicos que mesmo pagando um preço alto a concorrência de escrita pode ser necessária.

Uma das cosias que as pessoas menos gostam nele é porque ele não é cheio de utilidades prontas, então para quem quer gerenciar algo como um DBA, fazer coisas mais complexas terá que fazer de forma mais manual.

Existem soluções ainda mais rápidas, batendo 10x o SQLite, mas aí fica manual demais para a maioria das pessoas, sequer tem um SQL, tem que criar os índices quase de forma manual, tem que fazer todas as otimizações na mão, algo minúsculo até em tamanho de executável que bate Oracle e SQL Server em quase todos os cenários. Mas exige a pessoa ter mais domínio do que está fazendo.

https://www.tabnews.com.br/maniero/fa90f03f-da33-4098-a37b-31cdd20acfae.

S2


Farei algo que muitos pedem para aprender a programar corretamente, gratuitamente (não vendo nada, é retribuição na minha aposentadoria) (links aqui no perfil também).

1

concordo em tudo que você disse e acho que os pontos levantados são muito relevantes e também devem ser considerados quando se opta por usar ou não sqlite. obrigado por contribuir para a discussão.

2
2