O que é o SQLite e por que aparece em produção

SQLite e um banco de dados relacional embutido - sem servidor, sem daemon, sem configuração de rede. Todo o banco fica em um único arquivo no disco. E provavelmente o banco de dados mais implantado do mundo: esta dentro do seu celular, do navegador, do sistema operacional e de milhares de apps desktop. Mas rodar em produção como backend de uma aplicação web? Para muita gente isso soa como heresia.

Em julho de 2026, Júlia Evans publicou no seu blog (jvns.ca) o que aprendeu rodando SQLite em produção. O post virou tendência no Hacker News com mais de 240 pontos e 63 comentários - cheio de insights práticos que desenvolvedores acumulam só depois de bater a cabeça no mundo real. A discussão revelou que muito mais times usam SQLite em produção do que o senso comum sugere.

O argumento central e simples: para muitas aplicações web com mais leituras do que escritas e sem necessidade de acesso concorrente de múltiplos processos em máquinas diferentes, SQLite e suficiente, mais rápido em leituras locais e muito mais simples de operar. A pergunta certa não e "e produção, então não posso usar SQLite", mas "o meu caso de uso cabe nas limitações do SQLite?"

Como o SQLite funciona por dentro

SQLite e uma biblioteca C vinculada diretamente no seu processo. Não ha cliente e servidor separados - o código do banco roda no mesmo processo que o seu app. Isso elimina toda a latência de rede e serialização de protocolo que existe em bancos como PostgreSQL. Uma leitura local pode ser muito mais rápida em operações simples porque você eliminou vários saltos de comunicação e overhead de protocolo.

O mecanismo de armazenamento usa formato B-tree bem documentado. Por padrão, SQLite usa journal mode DELETE para controle de transações: antes de modificar uma página, ele escreve o conteúdo original em um arquivo de journal. Para produção, o WAL mode (Write-Ahead Logging) e muito melhor: em vez de bloquear leituras durante escritas, o WAL permite que leituras e escritas aconteçam simultaneamente sem bloqueio mutuo.

O modelo de concorrência tem uma limitação fundamental: apenas um escritor por vez. Se dois processos tentam escrever ao mesmo tempo, um deles espera com timeout configurável ou recebe um erro SQLITE_BUSY. Para leituras, o WAL mode permite múltiplos leitores simultâneos sem bloqueio. Esse modelo funciona bem para apps web típicos onde escritas são menos frequentes que leituras e onde um processo principal e o escritor.

Configurações essenciais para produção

A configuração padrão do SQLite não e otimizada para uso web. Existem PRAGMAs que fazem diferença enorme na prática. O mais importante e PRAGMA journal_mode=WAL. Ele muda o modelo de escrita e permite leituras concorrentes mesmo durante transações de escrita - essencial para qualquer app web com requisições simultâneas.

PRAGMA synchronous=NORMAL e outra configuração crítica. O padrão e FULL, que faz o SQLite esperar o dado chegar fisicamente no disco antes de continuar - seguro mas lento. NORMAL oferece durabilidade aceitável com performance muito melhor. Em caso de falha de energia, você pode perder no máximo a última transação, não o banco inteiro. Para a maioria dos casos de uso web, NORMAL e o trade-off certo.

PRAGMA busy_timeout=5000 e frequentemente esquecido. Sem ele, quando uma escrita colide com outra transação, o SQLite retorna SQLITE_BUSY imediatamente - gerando erros na sua aplicação. Com busy_timeout de 5000 milissegundos, o SQLite aguarda até 5 segundos antes de desistir, eliminando erros de concorrência em picos de carga normais.

💡
Dica

Configure esses PRAGMAs logo após abrir a conexão. Em Python com sqlite3: execute PRAGMA journal_mode=WAL, PRAGMA synchronous=NORMAL e PRAGMA busy_timeout=5000 antes de qualquer operação no banco.

Como começar: instalação e configuração passo a passo

SQLite já vem embutido na maioria das linguagens. Em Python, o módulo sqlite3 e parte da biblioteca padrão. Em Node.js, as opcoes mais populares são better-sqlite3 (síncrono, mais simples) e node-sqlite3 (assíncrono). Em Go, modernc.org/sqlite e o driver mais usado sem dependência de C externa.

O passo a passo inicial: (1) criar o arquivo de banco com nome claro; (2) configurar os PRAGMAs de produção na abertura da conexão; (3) habilitar foreign keys com PRAGMA foreign_keys=ON, desabilitado por padrão; (4) criar índices corretos - SQLite suporta índices normais, parciais e em expressões, e eles fazem tanta diferença aqui quanto em qualquer outro banco relacional.

Para apps web com vários requests simultâneos: use uma conexão por thread ou por request, não uma conexão global compartilhada. SQLite em WAL mode suporta vários leitores simultâneos, mas compartilhar uma única conexão entre threads pode gerar erros de concorrência. O padrão mais simples e criar a conexão no inicio de cada request e fechar ao final.

import sqlite3

def get_db_connection(db_path):
    conn = sqlite3.connect(db_path)
    conn.execute("PRAGMA journal_mode=WAL")
    conn.execute("PRAGMA synchronous=NORMAL")
    conn.execute("PRAGMA busy_timeout=5000")
    conn.execute("PRAGMA foreign_keys=ON")
    conn.execute("PRAGMA cache_size=-64000")  # 64MB cache em memoria
    conn.row_factory = sqlite3.Row
    return conn

Exemplo prático: SQLite como backend de API web

Imagine uma API de leitura de posts de blog com SQLite. Com WAL mode, você pode ter dezenas de requests de leitura simultâneos sem nenhuma contenção. Para um blog com 10.000 posts e 500 visitantes simultâneos, SQLite com configuração correta segura a carga com folga em um servidor moderno.

O cenário onde SQLite brilha em produção e exatamente esse: aplicação com muito mais leituras do que escritas, rodando em um único servidor. Um painel administrativo com relatórios, um catalogo de produtos, um sistema de gerenciamento de conteúdo de porte pequeno ou médio - todos funcionam muito bem com SQLite. Times reais tem relatado usar SQLite em produção por anos sem problemas em workloads desse perfil.

Para escritas, o WAL mode permite inserções e updates enquanto outros requests estão lendo. Se vários requests tentam escrever ao mesmo tempo, são serializados automaticamente com o busy_timeout garantindo que nenhum deles falha por colisão imediata. Em benchmarks, SQLite com WAL consegue entre 1.000 e 10.000 inserções por segundo dependendo do hardware e tamanho das transações.

# Verificar se WAL esta ativo
sqlite3 meu_banco.db "PRAGMA journal_mode;"
# Deve retornar: wal

# Analisar plano de execução de uma query
sqlite3 meu_banco.db "EXPLAIN QUERY PLAN SELECT * FROM posts WHERE category='tech';"

# Ver todos os índices
sqlite3 meu_banco.db ".indexes"

Comparação com PostgreSQL e MySQL

PostgreSQL e MySQL são bancos cliente-servidor: rodam como processo separado, aceitam conexões via socket e suportam múltiplos clientes escrevendo ao mesmo tempo com controle MVCC completo. São a escolha certa quando você tem vários processos de app em servidores diferentes, precisa de escritas altamente concorrentes ou quer recursos como replicação nativa, extensões avançadas ou funcionalidades avançadas de full-text search.

SQLite não compite com esses bancos para todos os casos - mas ganha com folga em outros. Leituras locais são mais rápidas porque eliminam a latência de rede e overhead de protocolo. Backup e simples: copie o arquivo. Migração entre ambientes e trivial. Não ha serviço para configurar, monitorar ou fazer upgrade. Para equipes pequenas ou projetos onde simplicidade operacional importa, esses benefícios são reais e mensuráveis.

Uma comparação prática: para uma aplicação com 100 requests por segundo onde 90 são leituras e 10 são escritas simples, SQLite com WAL vai performar igual ou melhor que PostgreSQL local e vai ser muito mais fácil de operar. Para uma aplicação com 500 escritas concorrentes por segundo de múltiplos servidores, PostgreSQL e claramente o caminho certo. O truque e saber em qual categoria seu projeto se encaixa.

Pontos positivos e limitações reais

Os pontos fortes do SQLite em produção são concretos: zero configuração de servidor, backup trivial com WAL mode ativo, latência de leitura mínima sem overhead de rede, suporte completo a SQL com transações ACID e operação segura em caso de crash com WAL mode. Para deploys simples em um único servidor, SQLite elimina uma camada inteira de infraestrutura que você não vai precisar gerenciar.

As limitações também são reais. Um escritor por vez e o limite principal - cargas de escrita muito concorrentes atingem esse teto. Sem acesso de múltiplos processos em máquinas diferentes - SQLite funciona com acesso local ao arquivo, então escalar horizontalmente com vários servidores de app escrevendo no mesmo banco não e possível sem uma camada extra de replicação.

ALTER TABLE no SQLite e limitado: adicionar colunas e simples, mas renomear ou remover colunas requer recriar a tabela. Recursos avançados como arrays nativos, JSONB com índices complexos ou full-text search sofisticado são mais limitados do que no PostgreSQL. Para casos de uso avançados de busca, o módulo FTS5 do SQLite funciona mas tem menos recursos que soluções dedicadas.

⚠️
Atenção

NUNCA coloque o arquivo SQLite em NFS, CIFS ou qualquer storage de rede compartilhado. O locking do SQLite depende de syscalls do sistema de arquivos que não funcionam corretamente via rede - isso causa corrupção silenciosa de dados. O arquivo DEVE ficar em disco local ou volume de bloco dedicado.

Casos de uso reais onde SQLite brilha em produção

APIs e backends de projetos pequenos e médios: Uma startup com menos de 100.000 usuários ativos e carga de escrita baixa pode rodar com SQLite por anos sem problema. Não ter um servidor PostgreSQL para gerenciar, fazer backup e monitorar e um beneficio operacional real, especialmente para times pequenos onde engenharia de plataforma não e o foco principal.

Ferramentas CLI e apps desktop: SQLite nasceu para isso. Qualquer app que precisa persistir dados localmente - gerenciadores de senhas, clientes de email, IDEs, ferramentas de desenvolvimento - usa SQLite de forma natural. A ausência de servidor e aqui uma vantagem absoluta, não uma limitação a contornar.

Ambientes de teste e CI/CD: Usar SQLite em testes unitários e de integração e uma prática comum mesmo em projetos que usam PostgreSQL em produção. Os testes rodam mais rápido, são isolados (cada teste pode ter seu banco em memoria) e não precisam de infraestrutura adicional no pipeline.

SQLite com Litestream para replicação: Litestream e uma ferramenta open source que replica o WAL do SQLite para S3 ou outro object storage em tempo quase real. Com Litestream, você tem backup continuo e pode recuperar para qualquer ponto no tempo - removendo uma das principais preocupações de usar SQLite em produção em termos de durabilidade e recuperação de desastres.

Dicas e boas práticas de quem já rodou em produção

🚀
Pro tip

Use transações explicitas para inserções em lote. BEGIN; INSERT...; INSERT...; COMMIT e muito mais rápido do que inserir linha por linha. Cada INSERT sem BEGIN abre e fecha uma transação implícita com fsync. Para inserir 100 mil registros, a diferença pode ser de 100x ou mais.

🚀
Pro tip

Use PRAGMA cache_size=-65536 para alocar 64MB de cache em memoria. O sinal negativo indica KB. Mais cache significa menos I/O em disco para leituras repetidas - especialmente útil para tabelas de referência acessadas com frequência por vários requests simultâneos.

💡
Dica

Use PRAGMA optimize periodicamente para atualizar as estatísticas internas do banco. Isso ajuda o query planner a tomar decisões melhores em queries com índices múltiplos. Pode ser chamado na abertura da conexão com uma frequência configurada.

🔴
Cuidado

NUNCA use SQLite em storage de rede como NFS ou EFS. O locking via rede não funciona de forma confiável e pode corromper o banco silenciosamente. Para produção em cloud, use um volume de bloco dedicado montado localmente na máquina.

Vale a pena usar SQLite em produção?

A resposta honesta e: sim, para mais aplicações do que você imagina. Se o seu app tem predominantemente leituras, roda em um único servidor e não precisa de funcionalidades avançadas que só o PostgreSQL oferece, SQLite e uma escolha legítima para produção com os benefícios reais de simplicidade operacional.

Os benefícios operacionais são concretos: não ter um servidor de banco separado significa menos serviço para monitorar, menos ponto de falha, backup trivial e setup mais simples para novos desenvolvedores. Para startups e projetos pequenos onde engenharia de plataforma não e o foco, isso tem valor real que vai além de preferência pessoal.

O próximo passo e avaliar seu projeto com honestidade: qual a relação de leituras vs escritas? Você precisa escalar horizontalmente com vários processos de app? Precisa de features avançadas de banco? Se as respostas indicam que SQLite cabe, experimente localmente com WAL mode e os PRAGMAs de produção configurados. A documentação oficial em sqlite.org/pragma.html e um dos melhores pontos de partida para configuração avançada.