SSD Nodes Learn Hosting plans →
Guias Matt ConnorPor Matt Connor · Atualizado 2026-08-07

SQLite em produção numa VPS: quando usar

Veja quando o SQLite funciona em produção numa VPS, como configurar WAL e busy_timeout, replicar com Litestream e identificar os limites de um único escritor.

Verified Every command ran end-to-end on a fresh Ubuntu 24.04 server, July 30, 2026.

Quando o SQLite é a base de dados de produção adequada numa VPS

Executar SQLite em produção numa VPS é a escolha certa para a maioria das aplicações pequenas, e o motivo é simples: um processo numa máquina a escrever num ficheiro não precisa de um servidor de base de dados. Não há nenhum daemon para supervisionar, nenhuma porta para filtrar, nenhuma palavra-passe para renovar e nenhuma segunda máquina para manter ativa. Uma consulta é uma chamada de função, e não uma viagem de ida e volta pela rede. Assim, uma página que executa quarenta consultas custa quarenta chamadas de função.

O custo é limitado, mas real. O SQLite permite apenas um escritor de cada vez em todo o ficheiro da base de dados, e o ficheiro não pode ser partilhado entre duas máquinas. Ambos os limites são aceitáveis para uma única VPS que execute uma única aplicação. Ambos tornam-se impeditivos assim que ultrapassa essa arquitetura. Este guia aborda as definições que tornam o SQLite seguro num servidor, as cópias de segurança contínuas com o Litestream e o ponto em que deve parar de usar esta solução.

Instale primeiro a ferramenta de linha de comandos. Tudo o que se segue foi executado no Ubuntu 24.04.

sudo apt update
sudo apt install -y sqlite3
sqlite3 --version

Isto apresenta uma versão que começa por 3., seguida de uma data de compilação e de um hash do código-fonte. O Ubuntu 24.04 inclui o SQLite 3.45.1 em julho de 2026. É provável que a sua aplicação não use este binário: a maioria dos runtimes de linguagens inclui a sua própria cópia da biblioteca SQLite, muitas vezes mais recente. Por isso, confirme a versão indicada pelo seu driver de base de dados antes de depender de uma funcionalidade recente.

Por que o modo WAL é a primeira alteração que deve fazer

Por predefinição, o SQLite utiliza um rollback journal. Antes de alterar uma página, copia a página original para um ficheiro -journal e depois edita a base de dados no local. Para fazer isso em segurança, obtém um bloqueio exclusivo sobre todo o ficheiro. Assim, todos os leitores ficam à espera enquanto qualquer operação de escrita está em curso. Num portátil, isto passa despercebido. Num servidor web, uma escrita lenta atrasa todos os pedidos que acedem à base de dados.

O modo WAL (write-ahead log) inverte a ordem. O processo de escrita acrescenta as páginas novas a um ficheiro -wal separado e deixa a base de dados principal inalterada. Os leitores continuam a ler o ficheiro principal no snapshot que existia quando iniciaram a leitura. Por isso, os leitores não bloqueiam o processo de escrita e o processo de escrita não bloqueia os leitores. Mais tarde, um checkpoint copia as páginas acumuladas no WAL para a base de dados principal. Esta alteração é responsável pela maior parte do que torna o SQLite utilizável por uma aplicação web.

Ative o modo WAL e confirme que ele foi mantido

mkdir -p ~/app
sqlite3 ~/app/app.db "PRAGMA journal_mode=WAL;"

O comando imprime wal. Essa saída não é apenas informativa. PRAGMA journal_mode devolve o modo em que o banco de dados está realmente a operar, portanto uma resposta delete significa que a alteração falhou e que o banco continua a usar o rollback journal.

O modo WAL é persistente. É uma definição no cabeçalho do banco de dados, não uma configuração da ligação. Por isso, execute-o uma vez por ficheiro de banco de dados. Todas as ligações seguintes herdam essa definição, inclusive depois de um reboot. Confirme isso com uma nova ligação.

sqlite3 ~/app/app.db "PRAGMA journal_mode;"

Agora crie uma tabela e veja o que aparece no disco.

sqlite3 ~/app/app.db <<'SQL'
CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT NOT NULL);
INSERT INTO notes (body) VALUES ('first row');
SQL
ls -l ~/app/

Agora existem três ficheiros: app.db, app.db-wal e app.db-shm. O ficheiro -wal contém páginas confirmadas que ainda não passaram por checkpoint. O ficheiro -shm é um índice de memória partilhada que cada ligação mapeia, para que todas concordem sobre o conteúdo do WAL. Ambos pertencem ao banco de dados e não são ficheiros temporários. Se copiar apenas app.db enquanto a aplicação está em execução, obterá um ficheiro sem cada confirmação recente. Se eliminar app.db e deixar os outros dois no lugar, o SQLite aplicará essas páginas WAL obsoletas ao novo ficheiro que surgir com esse nome. É assim que se corrompe um banco de dados novo ao tentar repor o anterior.

As configurações de ligação de que todas as aplicações de produção precisam

Apenas journal_mode é armazenado na base de dados. Todas as outras configurações abaixo são específicas de cada ligação. Isto significa que a aplicação tem de as executar em cada ligação que abrir, incluindo todas as ligações que um pool criar em segundo plano.

PRAGMA journal_mode = WAL;
PRAGMA busy_timeout = 5000;
PRAGMA synchronous = NORMAL;
PRAGMA foreign_keys = ON;

busy_timeout = 5000 indica ao SQLite que deve continuar a tentar aceder a uma base de dados bloqueada durante até 5000 milissegundos antes de devolver database is locked. O valor predefinido é 0. Por isso, por predefinição, o SQLite falha imediatamente quando dois processos de escrita se sobrepõem pela primeira vez. Definir este único valor elimina a maioria dos erros de bloqueio que são atribuídos ao próprio SQLite.

synchronous = NORMAL é a configuração correta no modo WAL, mas é importante compreender a compensação. Com FULL, o SQLite chama fsync no WAL em cada commit. Com NORMAL, sincroniza nos checkpoints. A documentação do SQLite é clara sobre o que se perde: as transações deixam de ser duráveis depois de uma falha de energia ou de um reset forçado. Essa perda de energia não corrompe a base de dados. Apenas perde os últimos commits que ainda não tinham sido gravados no disco. Numa VPS, esta é normalmente a compensação correta, porque retira um fsync do caminho de cada escrita.

foreign_keys = ON está desativado por predefinição por motivos de compatibilidade com versões anteriores e é específico de cada ligação. Um schema cheio de cláusulas REFERENCES não aplica nenhuma restrição até que cada ligação ative esta opção.

Há ainda uma configuração que só será relevante mais tarde. O SQLite cria checkpoints automaticamente quando o WAL ultrapassa 1000 páginas, e a operação é executada pela ligação que, por acaso, concluir uma transação nesse momento. Isto não causa problemas por si só. Torna-se uma questão quando o Litestream está em execução, porque o Litestream precisa de controlar quando os checkpoints ocorrem.

Por que database is locked continua ocorrendo depois de definir busy_timeout

Este é o problema que faz as pessoas voltarem para o Postgres, e tem uma causa específica.

Um busy timeout instala um busy handler, mas o SQLite não garante que vai chamá-lo.

Se o SQLite determinar que chamar o busy handler pode resultar em um deadlock, ele retornará SQLITE_BUSY à aplicação em vez de chamar o busy handler.

O deadlock que ele evita ocorre quando uma transação é promovida. Um BEGIN sem argumentos no SQLite significa BEGIN DEFERRED. Se a primeira instrução depois dele for um SELECT, você estará numa transação de leitura. Quando um UPDATE posterior, na mesma transação, precisar ser convertido numa transação de escrita e outra conexão tiver escrito desde o início da sua leitura, o SQLite não poderá fazer você esperar. O seu snapshot já estará desatualizado, e esperar apenas faria as duas conexões entrarem num deadlock entre si. A documentação declara o resultado diretamente:

As instruções de escrita subsequentes promoverão a transação para uma transação de escrita, se possível, ou retornarão SQLITE_BUSY.

O seu timeout de 5000 milissegundos nunca é consultado. O erro chega imediatamente, por isso parece que a configuração não teve efeito.

A correção tem uma palavra.

BEGIN IMMEDIATE;
UPDATE notes SET body = 'edited' WHERE id = 1;
COMMIT;

BEGIN IMMEDIATE obtém o bloqueio de escrita no início, antes de ler qualquer coisa. Não há promoção, portanto não há deadlock a evitar. O busy handler é aplicado, e a conexão espera pela sua vez em vez de falhar. Mantenha adiadas as transações somente de leitura. Qualquer transação que contenha uma escrita deve ser immediate.

A segunda causa dos erros de bloqueio é mais difícil de identificar: manter uma transação de escrita aberta durante uma operação lenta. O SQLite serializa os escritores, portanto uma transação que é aberta, chama uma API externa pela rede e depois faz commit bloqueará todos os outros escritores durante toda essa chamada. Leia o que precisar, feche a transação, faça a operação lenta e depois abra uma transação de escrita curta para armazenar o resultado.

Backup contínuo com Litestream

Uma cópia noturna pode perder até um dia de gravações, e executar cp contra uma base de dados SQLite ativa pode produzir uma cópia que não abre. Há duas opções seguras. sqlite3 app.db ".backup /path/to/backup.db" usa a interface de backup online do SQLite e funciona com uma base de dados em utilização. O Litestream vai mais longe: monitoriza o WAL e envia continuamente as alterações para armazenamento de objetos. Assim, a perda de dados no pior caso passa de um dia para cerca de um segundo.

O Litestream é um único binário Go que é executado junto da aplicação. Não fica entre a aplicação e a base de dados. A aplicação grava no SQLite exatamente como antes, e o Litestream lê o WAL e envia o que mudou.

cd /tmp
curl -fsSL -O https://github.com/benbjohnson/litestream/releases/download/v0.5.14/litestream-0.5.14-linux-x86_64.deb
sudo dpkg -i litestream-0.5.14-linux-x86_64.deb
litestream version

v0.5.14 é a versão documentada na página oficial de instalação para Linux em julho de 2026, e v0.5.15 foi lançada em 21 de julho de 2026. Altere a versão nas duas linhas para corresponder à tag atual na página de releases e use o pacote arm64 correspondente se o seu VPS for arm64.

O ficheiro de configuração fica em /etc/litestream.yml. Comece com uma réplica local. Isto valida todo o processo sem exigir credenciais de cloud.

dbs:
  - path: /home/appuser/app/app.db
    replica:
      type: file
      path: /var/backups/litestream/app

Observe que o campo é replica, no singular. O Litestream 0.5 substituiu o array replicas da série 0.3 por um único bloco de réplica. Uma configuração com duas entradas agora falha ao arrancar. Muitos guias de terceiros ainda mostram o array antigo. Por isso, copie a estrutura acima em vez do primeiro exemplo que aparecer numa pesquisa. A série 0.5 também mudou o nome do subcomando litestream wal para litestream ltx, porque o formato do backup em disco foi alterado.

Verifique se a configuração é analisada corretamente antes de ativar qualquer coisa.

sudo litestream databases -config /etc/litestream.yml

Depois, valide manualmente o ciclo completo. Este formato ignora o ficheiro de configuração e replica uma base de dados para um único caminho.

mkdir -p /tmp/replica
litestream replicate ~/app/app.db file:///tmp/replica/app

O comando é executado em primeiro plano e continua em execução. Numa segunda shell, grave uma linha e restaure a réplica para um ficheiro novo.

sqlite3 ~/app/app.db "INSERT INTO notes (body) VALUES ('written after replication started');"
litestream restore -o /tmp/restored.db file:///tmp/replica/app
sqlite3 /tmp/restored.db "SELECT count(*) FROM notes;"

A contagem inclui a nova linha. Se isso não acontecer, a alteração ainda não foi sincronizada. O Litestream envia as alterações num sync-interval que, por predefinição, é de 1 segundo. Aguarde e faça a restauração novamente. Esse segundo também é o seu ponto de recuperação. Uma falha perde, no máximo, as gravações do último intervalo de sincronização. Nenhuma configuração elimina essa perda por completo.

Para usar armazenamento real, substitua o bloco de réplica por um URL S3. Isto funciona com o Amazon S3 e com armazenamento de objetos compatível com S3 de outros fornecedores.

dbs:
  - path: /home/appuser/app/app.db
    replica:
      url: s3://your-bucket-name/app
      region: us-east-1

snapshot:
  interval: 24h
  retention: 24h

Não guarde as credenciais nesse ficheiro. O Litestream lê LITESTREAM_ACCESS_KEY_ID e LITESTREAM_SECRET_ACCESS_KEY do ambiente. Por isso, coloque-as num drop-in do systemd pertencente a root e com o modo 600.

Os valores de snapshot acima são os valores predefinidos, mas o valor de retenção costuma causar surpresas. A retenção define durante quanto tempo o Litestream mantém os snapshots e os ficheiros associados. Também define até que ponto no passado pode fazer uma restauração. Vinte e quatro horas significa que uma migração incorreta detetada na quarta-feira de manhã já não pode ser recuperada a partir do estado de segunda-feira. Defina retention: 168h para uma semana e aceite o custo do armazenamento adicional.

Comprove a restauração antes de precisar dela

litestream restore -o /tmp/check.db /home/appuser/app/app.db
sqlite3 /tmp/check.db "PRAGMA integrity_check;"
sqlite3 /tmp/check.db "SELECT count(*) FROM notes;"

Dado um caminho de base de dados, litestream restore procura a réplica correspondente em /etc/litestream.yml e descarrega-a. PRAGMA integrity_check apresenta ok num ficheiro íntegro; qualquer outra saída significa que a cópia restaurada não é utilizável. Execute este procedimento regularmente com um serviço e um temporizador systemd e leia a saída. Enquanto não restaurar uma cópia de segurança pelo menos uma vez, não saberá se ela funciona.

Executar o Litestream com systemd

O pacote Debian instala uma unidade litestream que lê /etc/litestream.yml.

sudo systemctl enable litestream
sudo systemctl start litestream
sudo journalctl -u litestream -f

Uma saída normal identifica cada base de dados definida na configuração e depois permanece silenciosa, exceto pelas linhas de sincronização periódicas. Um erro no such file or directory relativo ao caminho da base de dados significa que o caminho na configuração está incorreto ou que o processo não consegue lê-lo. Por predefinição, a unidade é executada como root, o que concede mais privilégios do que esta tarefa necessita. O Litestream tem de conseguir ler e escrever tanto na base de dados como no diretório que a contém, porque trabalha com os ficheiros -wal e -shm junto à base de dados. Por isso, atribua-lhe a conta que a sua aplicação já utiliza.

# /etc/systemd/system/litestream.service.d/override.conf
[Service]
User=appuser
Group=appuser

Aplique a configuração com sudo systemctl daemon-reload e sudo systemctl restart litestream. Configurar uma conta de serviço dedicada com privilégios mínimos demora alguns minutos. Essa configuração distingue um agente de backup de um segundo processo root no servidor.

Há um detalhe importante na ordem de arranque se alguma vez reconstruir a máquina do zero. A base de dados deve ser restaurada antes de a aplicação arrancar. litestream restore aceita -if-db-not-exists, que termina com o código 0 quando o ficheiro já existe. Por isso, é seguro executá-lo em todos os boots. Coloque-o numa linha ExecStartPre na unidade da aplicação. Assim, um VPS novo transfere a base de dados, enquanto um VPS existente não faz nada. litestream replicate tem uma flag -restore-if-db-not-exists correspondente, caso prefira manter tudo num só local.

Onde o SQLite falha numa VPS

Sistemas de ficheiros de rede. Este é o limite que não pode contornar com configuração. O modo WAL exige que todos os processos que usam a base de dados partilhem uma pequena região de memória, fornecida pelo ficheiro -shm. A documentação do SQLite estabelece esta regra sem exceções:

Todos os processos que usam uma base de dados têm de estar no mesmo computador anfitrião; o WAL não funciona através de um sistema de ficheiros de rede.

Por isso, uma base de dados num NFS montado (sistema de ficheiros de rede) ou numa partilha SMB pode ficar corrompida, e nenhum pragma impede esse problema. Há uma distinção que muitas pessoas ignoram. Um dispositivo de blocos de rede, que é o tipo de armazenamento adicional que a maioria dos fornecedores de VPS disponibiliza, aparece no Linux como um disco normal com um sistema de ficheiros normal, e isso funciona. Uma partilha de ficheiros montada é diferente.

Um segundo servidor de aplicações. Nenhuma configuração faz isto funcionar. Assim que precisar de duas máquinas a servir os mesmos dados, precisará de uma base de dados que comunique através da rede. Tome essa decisão enquanto ainda tiver tempo para a planear.

Cargas de trabalho com muitas escritas. Um escritor de cada vez é uma propriedade do formato de ficheiro, não uma opção ajustável. As escritas curtas são rápidas porque cada commit é acrescentado ao WAL, por isso o débito depende mais da latência do disco em escritas pequenas do que do CPU. Consulte NVMe versus armazenamento SSD SATA numa VPS para ver o impacto dessa diferença. O verdadeiro problema são as transações longas, porque colocam todos os outros escritores em espera.

Consultas analíticas. O SQLite é um armazenamento orientado a linhas, concebido para transações. Um dashboard que percorre cem milhões de linhas é uma tarefa diferente, adequada a outra ferramenta. DuckDB comparado com SQLite para trabalho em servidores explica onde fica essa fronteira.

VACUUM durante a replicação. Um VACUUM completo reescreve todo o ficheiro da base de dados. Por isso, o Litestream tem de o enviar novamente na totalidade, e a documentação do Litestream recomenda não executar esta operação enquanto a replicação estiver ativa. Pare o replicador, execute o vacuum, inicie-o novamente e conte com um novo snapshot completo.

Dois replicadores na mesma base de dados. Nunca execute dois processos Litestream na mesma base de dados ou no mesmo destino de réplica. A documentação afirma explicitamente que cabe a si impedir esta situação. Caso contrário, obterá uma réplica que não poderá restaurar.

O que o Litestream não cobre

O Litestream protege apenas o ficheiro da base de dados. Os ficheiros carregados, a configuração da aplicação, os certificados TLS (transport layer security) e os ficheiros de unidade continuam a ser da sua responsabilidade. Combine-o com cópias de segurança encriptadas fora do servidor com restic executadas regularmente para cobrir ambas as partes. Se a máquina for nova, os primeiros dez minutos num VPS novo abrangem a configuração da conta de utilizador e da firewall que este guia pressupõe estar concluída.

FAQ

O SQLite é suficiente para uma aplicação em produção?

Para uma aplicação num único servidor, sim, desde que ative o modo WAL, defina um tempo limite de espera e faça cópias de segurança contínuas. Os limites relevantes são estruturais: apenas um processo de escrita de cada vez e uma única máquina anfitriã. Uma aplicação que se enquadre nesses limites obtém uma base de dados sem salto de rede e sem um processo separado para monitorizar. Uma aplicação que não se enquadre precisa de uma base de dados cliente-servidor. Nenhum ajuste altera esse facto.

Porque continuo a receber database is locked depois de definir busy_timeout?

Porque o SQLite ignora o gestor de espera quando aguardar pode causar um deadlock. Uma transação que começa com um BEGIN simples é adiada: um SELECT inicial coloca-a numa transação de leitura, e uma escrita posterior tem de fazer uma atualização. Se outra ligação escrever entretanto, o SQLite devolve SQLITE_BUSY imediatamente em vez de chamar o gestor de espera, porque o snapshot de leitura já está desatualizado. Inicie com BEGIN IMMEDIATE qualquer transação que vá escrever, para que o bloqueio de escrita seja obtido logo no início e o tempo limite seja aplicado.

Posso manter a minha base de dados SQLite num armazenamento de rede?

Não num sistema de ficheiros de rede, como NFS ou SMB. O modo WAL precisa que todos os processos partilhem memória através do ficheiro -shm, e a documentação do SQLite indica que todos os processos que usam a base de dados têm de estar no mesmo computador anfitrião. Um dispositivo de blocos de rede ligado pelo fornecedor é diferente: o Linux vê um disco normal com um sistema de ficheiros normal, e o SQLite funciona nesse disco.

Preciso do Litestream se já faço cópias de segurança noturnas?

Depende da quantidade de dados que pode perder. Um trabalho noturno pode significar perder até vinte e quatro horas de escritas. O Litestream sincroniza aproximadamente uma vez por segundo, portanto uma falha custa-lhe cerca do último segundo. Também é mais seguro do que copiar o ficheiro da base de dados com cp, que pode capturar a base de dados durante uma escrita. O Litestream cobre apenas a base de dados, por isso mantenha também uma cópia de segurança geral dos ficheiros.

#sqlite#wal#litestream#backups#production