Pular para conteúdo

ADR-002: SQLite como banco de dados principal

Campo Valor
Status Aceito
Data 2024
Autor Caio Fiori Martins

Contexto

Com a decisão de operar local-first (ADR-001), foi necessário escolher um banco de dados que rodasse sem infraestrutura adicional na máquina do servidor local. A escolha do banco tem implicações diretas em:

  • Portabilidade — o servidor precisa rodar em qualquer notebook
  • Operação zero-config — a organização do evento não tem DBA
  • Confiabilidade — o banco não pode corromper dados em caso de desligamento abrupto
  • Performance — precisa responder em < 50ms para o volume esperado

Carga esperada

O dimensionamento é crítico para justificar a escolha. Um evento escolar típico tem:

  • 10–30 lojas simultâneas
  • 200–800 participantes
  • Duração de 4–8 horas
  • Pico estimado: ~5 transações/segundo

Esse volume é trivial para qualquer banco de dados relacional. É importante nomear isso explicitamente: a escolha não é "SQLite porque é o que aguenta" — é "SQLite porque é exatamente o que o problema pede".


Decisão

SQLite é o banco de dados principal do Ouroboros.


Justificativa

1. Zero infraestrutura

SQLite é uma biblioteca, não um servidor. Não há processo separado pra iniciar, porta pra abrir, autenticação pra configurar. O banco é um único arquivo .db no sistema de arquivos. Isso elimina uma classe inteira de problemas operacionais.

2. Durabilidade por design

SQLite é ACID compliant com WAL (Write-Ahead Logging) habilitado. Em caso de queda de energia ou desligamento abrupto, o banco recupera automaticamente o estado consistente na próxima abertura. Nenhuma transação confirmada é perdida.

3. Performance adequada

SQLite com WAL mode suporta facilmente 1.000+ writes/segundo em hardware moderno. Para 5 transações/segundo, há margem de 200x. A latência de escrita é determinada pelo disco local — tipicamente 1–5ms — comparado aos 100–400ms de um banco remoto.

4. Portabilidade

O banco inteiro é um arquivo. Backup = copiar o arquivo. Migração = mover o arquivo. Debug = abrir no DB Browser for SQLite. Não há abstração opaca entre o operador e os dados.

5. Adoção em produção real

SQLite é o banco de dados mais deployado do mundo. É usado em produção por WhatsApp (mensagens locais), browsers (IndexedDB), iOS e Android (armazenamento nativo), e aplicações Electron. A narrativa de que "SQLite é só pra desenvolvimento" é incorreta para casos de uso de baixa concorrência write.


Alternativas consideradas

PostgreSQL

Descartado: requer processo separado, instalação, configuração de autenticação. Aumenta o setup significativamente sem benefício para a carga esperada. Faz sentido quando você precisa de concorrência massiva de writes ou features avançadas (JSONB, full-text search, etc.).

MongoDB

Descartado: sem schema rígido é desvantagem aqui — o modelo de dados do Ouroboros é bem definido e relacional. Adiciona complexidade (processo separado, driver) sem vantagem.

Redis

Descartado: excelente como cache, não adequado como store primário persistente para event sourcing.

In-memory (dicionário Python)

Descartado: perde todo o estado em qualquer restart ou queda de energia. Inaceitável pelo plano de resiliência.


Consequências

Positivas:

  • Setup em 30 segundos (literalmente)
  • Backup trivial
  • Debug direto no arquivo
  • Zero custo de infraestrutura
  • Comportamento previsível e bem documentado

Negativas e trade-offs aceitos:

  • Writes são serializados — sem concorrência paralela de escrita. Aceitável para a carga esperada; inaceitável para sistemas com milhares de writes simultâneos
  • Não adequado para deploy multi-servidor (cada servidor teria sua própria cópia)

Limite claro de escopo

SQLite é a escolha certa para esse problema específico. Se o Ouroboros evoluísse para um produto SaaS multi-tenant com milhares de eventos simultâneos, a migração para PostgreSQL seria necessária. Essa é uma decisão consciente de escopo, não uma limitação técnica ignorada.


Configuração recomendada

# pragma statements para maximizar performance e durabilidade
PRAGMA journal_mode = WAL;      -- habilita WAL mode
PRAGMA synchronous = NORMAL;    -- balanceia durabilidade e performance
PRAGMA foreign_keys = ON;       -- garante integridade referencial
PRAGMA cache_size = -64000;     -- 64MB de cache em memória