Litestream: replicação de SQLite em tempo real para backups confiáveis

1. O Problema dos Backups em SQLite em Produção

1.1. Limitações tradicionais

O SQLite é amplamente utilizado em produção, mas seus mecanismos tradicionais de backup apresentam sérias limitações. O comando .backup do sqlite3 requer bloqueio exclusivo de escrita durante toda a operação, o que pode paralisar aplicações por segundos ou minutos em bancos de dados grandes. Pior: se o processo falhar durante o backup, o arquivo resultante pode ficar corrompido e inutilizável.

# Backup tradicional com sqlite3 (bloqueia escrita)
sqlite3 /var/data/app.db ".backup /backups/app_$(date +%Y%m%d).db"

1.2. Cenários onde SQLite é usado em produção

SQLite brilha em ambientes com recursos limitados: dispositivos embarcados, servidores edge, aplicações single-server, prototipagem rápida e sites estáticos que precisam de persistência. Nesses cenários, um banco PostgreSQL ou MySQL seria superdimensionado e complexo demais para gerenciar.

1.3. A necessidade de replicação contínua e PITR

Backups diários não são suficientes para aplicações que processam dados continuamente. A perda de algumas horas de dados pode ser catastrófica. A recuperação point-in-time (PITR) permite restaurar o banco para qualquer momento específico, não apenas para o último backup completo.

2. O que é o Litestream e Como Funciona

2.1. Conceito de replicação baseada em WAL

O Litestream aproveita o Write-Ahead Log (WAL) do SQLite. Quando o SQLite opera em modo WAL (padrão desde a versão 3.7.0), todas as modificações são escritas primeiro em um arquivo de log separado antes de serem aplicadas ao banco principal. O Litestream monitora continuamente esse arquivo WAL, capturando cada alteração em tempo real.

2.2. Fluxo de replicação

O processo é simples e eficiente:

  1. O SQLite escreve alterações no arquivo WAL
  2. O Litestream detecta as novas páginas modificadas
  3. As páginas são compactadas e enviadas incrementalmente para o destino configurado
  4. Periodicamente, um snapshot completo do banco é gerado
# Fluxo básico de replicação
SQLite app.db → app.db-wal (WAL) → Litestream → S3/MinIO/GCS

2.3. Diferenças para soluções concorrentes

Diferente do rqlite (que cria um cluster Raft sobre SQLite) ou do Dqlite (que substitui o SQLite vanilla), o Litestream não altera o comportamento do SQLite. Ele opera como um processo separado, sem modificar o banco de dados original. É uma solução de backup, não de alta disponibilidade.

3. Arquitetura e Modos de Operação

3.1. Modo replicação contínua vs. snapshot periódico

O Litestream opera em dois modos complementares:

  • Replicação contínua: envia páginas WAL modificadas para o destino em tempo real (intervalo de ~1 segundo)
  • Snapshot periódico: gera uma cópia completa do banco em intervalos configuráveis (recomendado a cada 24h ou 100MB de WAL)
# Configuração típica: snapshot a cada 6 horas
snapshot-interval: 6h

3.2. Destinos suportados

O Litestream suporta múltiplos backends de armazenamento:

  • Amazon S3 (e compatíveis como MinIO)
  • Backblaze B2
  • Google Cloud Storage
  • Azure Blob Storage
  • Sistema de arquivos local (para testes)

3.3. Estratégia de retenção

A retenção inteligente mantém snapshots e logs WAL por períodos configuráveis, permitindo PITR dentro da janela de retenção:

# Manter snapshots por 30 dias, logs WAL por 7 dias
retention:
  snapshots: 30d
  wal: 7d

4. Instalação e Configuração Essencial

4.1. Instalação

# Linux (amd64)
curl -L https://github.com/benbjohnson/litestream/releases/download/v0.3.13/litestream-v0.3.13-linux-amd64.tar.gz | tar xz
sudo mv litestream /usr/local/bin/

# macOS
brew install litestream

4.2. Estrutura do arquivo de configuração

O Litestream usa um arquivo YAML para configuração. Exemplo completo:

# /etc/litestream.yml
dbs:
  - path: /var/data/app.db
    replicas:
      - url: s3://my-bucket/backups/app
        access-key-id: AKIAIOSFODNN7EXAMPLE
        secret-access-key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
        region: us-east-1
    snapshot-interval: 24h
    validation-interval: 1h

4.3. Exemplo prático: SQLite local → bucket S3

# 1. Criar bucket S3 (via AWS CLI)
aws s3 mb s3://meus-backups-sqlite

# 2. Configurar Litestream
cat > /etc/litestream.yml << 'EOF'
dbs:
  - path: /var/lib/app/database.sqlite
    replicas:
      - url: s3://meus-backups-sqlite/app-producao
        access-key-id: ${AWS_ACCESS_KEY_ID}
        secret-access-key: ${AWS_SECRET_ACCESS_KEY}
        region: us-east-1
EOF

# 3. Iniciar o serviço
litestream replicate -config /etc/litestream.yml

5. Restauração e Recuperação de Desastres

5.1. Restauração básica

# Restaurar para o estado mais recente
litestream restore -o /tmp/app-restaurado.db s3://meus-backups-sqlite/app-producao

5.2. Restauração point-in-time

# Restaurar para um momento específico
litestream restore -o /tmp/app-2024-01-15.db \
  -timestamp "2024-01-15T14:30:00Z" \
  s3://meus-backups-sqlite/app-producao

# Usando geração (posição do log WAL)
litestream restore -o /tmp/app-generacao-42.db \
  -generation 42 \
  s3://meus-backups-sqlite/app-producao

5.3. Recuperação para diretório diferente e verificação

# Restaurar e verificar integridade
litestream restore -o /data/novo-app.db \
  s3://meus-backups-sqlite/app-producao

# Verificar integridade do banco restaurado
sqlite3 /data/novo-app.db "PRAGMA integrity_check;"

6. Integração com Aplicações e Práticas Recomendadas

6.1. Uso com frameworks

Em aplicações Rails, configure o caminho do banco no database.yml:

# config/database.yml
production:
  adapter: sqlite3
  database: /var/data/app_production.sqlite3
  pool: 5
  timeout: 5000

Para Node.js com better-sqlite3:

const Database = require('better-sqlite3');
const db = new Database('/var/data/app.db', {
  wal: true,
  timeout: 5000
});

6.2. Monitoramento

# Verificar status da replicação
litestream status

# Logs do Litestream (systemd)
journalctl -u litestream -f

# Métricas importantes no log:
# "replicating" - indica replicação ativa
# "snapshot created" - snapshot completo gerado
# "error" - problemas de conexão com o storage

6.3. Boas práticas

# Configuração recomendada para produção
snapshot-interval: 6h       # Reduzir para 1h em bancos críticos
wal-max-size: 100MB         # Evitar WAL muito grande
validation-interval: 1h     # Verificar integridade periodicamente

# Rotação de backups: configurar lifecycle no S3
aws s3api put-bucket-lifecycle-configuration \
  --bucket meus-backups-sqlite \
  --lifecycle-configuration '{
    "Rules": [{
      "ID": "expire-old-snapshots",
      "Filter": {"Prefix": "app-producao/snapshots"},
      "Status": "Enabled",
      "Expiration": {"Days": 90}
    }]
  }'

7. Limitações, Riscos e Casos de Uso Reais

7.1. Limitações

  • Replicação assíncrona: pode haver perda de até alguns segundos de dados em caso de falha catastrófica
  • Sem failover automático: o Litestream não substitui o banco original automaticamente
  • Overhead de I/O: a replicação contínua adiciona carga de leitura no disco

7.2. Riscos

# Risco: concorrência com VACUUM
# O VACUUM tradicional pode causar inconsistências no WAL
# Solução: usar PRAGMA auto_vacuum = INCREMENTAL em vez de VACUUM

# Risco: perda de dados na janela de replicação
# Em média, 1-2 segundos de dados podem ser perdidos
# Mitigação: configurar checkpoint frequente no SQLite
PRAGMA wal_autocheckpoint = 1000;  # Checkpoint a cada 1000 páginas

7.3. Casos de uso reais

  • Edge computing: dispositivos IoT que coletam dados localmente e replicam para a nuvem
  • Sites estáticos com SQLite: blogs, documentações e sites pessoais que precisam de backup confiável
  • Servidores single-node: aplicações internas, dashboards, ferramentas de administração
  • Ambientes de desenvolvimento: backup automático de bancos de desenvolvimento sem complexidade

Referências