- Ben Johnson, criador do BoltDB (um banco de dados key-value embarcado), agora está desenvolvendo o Litestream na Fly.io
- A estrutura mais comum de uma aplicação full stack é n-Tier: servidor de aplicação + servidor de banco de dados
→ Nessa arquitetura, o SQLite era usado só para testes unitários, mas agora já pode ser usado perfeitamente como camada de dados e persistência - O Litestream é um projeto open source que torna possível usar SQLite em aplicações full stack por meio de replicação
Uma breve história dos bancos de dados de aplicações
-
50 anos talvez não pareçam tanto tempo, mas houve mudanças enormes na forma como o software gerencia dados
→ Nos anos 70, surgiram as “regras de Codd”, que definiram o banco de dados relacional
→ Todos os dados ficam em tabelas, com CRUD, esquema, linguagem SQL etc.
→ Nos anos 80 e 90, bancos SQL como Oracle/DB2/Postgres/MySQL explodiram em popularidade
→ Nos anos 2000, os bancos XML não foram bons, enquanto ao mesmo tempo surgiram excelentes bancos colunares
→ Nos anos 2010, foram lançados grandes projetos open source de bancos distribuídos, e agora qualquer um pode montar um cluster e consultar dados na escala de terabytes -
À medida que os bancos evoluíram, as estratégias para conectá-los às aplicações também evoluíram
→ Desde Codd, eles foram sendo separados em tiers
→ Primeiro veio o tier do banco de dados
→ Depois, o tier de cache com memcached e Redis
→ Tier de jobs em background (Sidekiq), tier de roteamento (PgBouncer), tier de distribuição etc.
→ Muitos tutoriais falam como se tudo fosse 3-Tier, mas como nunca se sabe quantos tiers vão entrar, o termo mais correto é “n-Tier” -
Ao longo desses 50 anos, também vimos CPU, memória e disco ficarem centenas de vezes mais rápidos e baratos
→ A palavra que de fato definiu a inovação em bancos de dados nos anos 2010 foi “big data”
→ Mas com a melhora do hardware, em 2020 esse conceito já ficou mais difícil de sustentar
→ Em 1996, gerenciar um banco de 1 GB era um grande feito; em 2022, isso roda tranquilamente até em um notebook ou numa instância t3.micro -
Quando pensamos em novas arquiteturas de banco de dados, ficamos hipnotizados pelos limites de escalabilidade
→ Se não consegue lidar com petabytes, ou pelo menos terabytes, nem entra na conversa
→ Mas a maioria das aplicações, mesmo quando faz sucesso, dificilmente chega a ter dados em escala de terabytes
→ Estamos usando uma britadeira para pregar um prego
O doce lançamento do SQLite
- Existe um banco de dados que reflete muito bem essa tendência
- É um dos bancos SQL mais famosos do mundo, é um formato oficial de preservação da Biblioteca do Congresso dos EUA, é conhecido pela confiabilidade e por uma suíte de testes gigantesca, além de ter desempenho excelente
- Nem precisaria dizer o nome, mas para quem está lá no fundo levantando a mão... estamos falando do SQLite
- SQLite é um banco de dados embarcado. Ele não existe como um tier separado na arquitetura tradicional; é simplesmente uma biblioteca linkada ao processo do seu servidor de aplicação
→ Uma “aplicação de processo único” que roda sozinha, sem depender de outro servidor
- Como eu sou alguém que cria bancos de dados, naturalmente me interesso por esse tipo de aplicação
- Eu criei o BoltDB, um banco key/value embarcado bastante conhecido no ecossistema Go
- O BoltDB é estável e entrega aquele desempenho de carrinho com nitro que você espera de um banco in-process
- Mas o BoltDB tem limitações
→ Como o esquema é definido em código Go, migrações de banco ficam difíceis. Você mesmo precisa criar as ferramentas. Nem REPL existe
- Se você tomar cuidado, esse tipo de banco pode entregar desempenho absurdo
- Mas, para uso geral, provavelmente você não vai querer operar um banco assim
- Fiquei pensando no que seria necessário para tornar o BoltDB viável em mais aplicações, e a conclusão a que cheguei foi: “o SQLite foi feito exatamente para isso”
- Claro que o SQLite também tem limitações. A principal é que uma aplicação de processo único tem um SPOF (Single Point of Failure): se você perde o servidor, perde também o banco de dados. Isso não é defeito do SQLite; ele foi projetado assim
Entra em cena o Litestream
- Há dois grandes motivos pelos quais muita gente não usa SQLite como padrão
→ Primeiro, resiliência contra falhas de armazenamento
→ Segundo, concorrência em escala maior - E o Litestream tem algo a dizer sobre esses dois pontos
- O Litestream funciona controlando o journaling do SQLite no modo WAL (Write Ahead Log)
- No modo WAL, as operações de escrita são adicionadas a um arquivo de log separado do arquivo principal do banco
- Os readers verificam tanto o arquivo WAL quanto o banco principal para atender às consultas
- Normalmente, o SQLite executa checkpoints automáticos para mover páginas do WAL para o banco principal
- O Litestream entra justamente nesse ponto: abre uma transação de leitura infinita para impedir o checkpoint automático, captura e replica diretamente as atualizações do WAL e aciona ele mesmo os checkpoints
A coisa mais importante para entender sobre o Litestream é que ele é simplesmente SQLite. A aplicação usa SQLite padrão; ele não adiciona dependências, não analisa queries e não funciona como proxy. Ele apenas aproveita os recursos de journaling e concorrência que o SQLite já tem. Na maioria dos casos, seu código talvez nem perceba que o Litestream existe
- Parece complexo, mas na prática é extremamente simples. Quando você usa, percebe que simplesmente funciona
$ litestream replicate fruits.db s3://my-bukkit:9000/fruits.db
$ litestream restore -o fruits-replica.db s3://my-bukkit:9000/fruits.db
- Em geral, as pessoas usam isso para replicar bancos SQLite e armazená-los no S3
- Isso traz uma grande vantagem operacional. Seu banco fica resiliente e fácil de mover ou migrar
- Mas dá para fazer mais com o Litestream
- Na próxima versão, será possível fazer replicação em tempo real entre bancos SQLite, o que permitirá configurar réplicas de leitura distribuídas e um banco líder de escrita
→ As réplicas de leitura poderão capturar escritas e redirecioná-las para o líder
→ Como muitas aplicações são read-heavy, essa configuração pode oferecer a elas um banco de dados escalável globalmente
Você deveria levar essa opção a sério: usar SQLite como banco da aplicação
- Um dos meus primeiros empregos em TI foi como DBA de Oracle no começo dos anos 2000
- Passei muito tempo lendo livros e documentação para aprender Oracle
- O manual de administração tinha quase mil páginas, e era só um entre centenas de documentos
- Aprender a otimizar queries ou melhorar escritas fazia uma diferença enorme naquela época
- Como os discos rígidos liam só algumas dezenas de megabytes por segundo, usar índices melhores podia transformar uma query de 5 minutos em uma query de 30 segundos
- Mas otimização de banco de dados está se tornando cada vez menos importante para aplicações comuns
- Se você tem um banco de 1 GB, um disco NVMe consegue colocar tudo em memória em menos de um segundo
- Eu adoro otimização de SQL, mas para muitos desenvolvedores de aplicações isso está virando uma habilidade em declínio
- Mesmo queries mal ajustadas conseguem rodar em menos de 1 segundo em muitos bancos
- O Postgres moderno é um milagre. Aprendi muito lendo esse código ao longo dos anos
- Otimizador de queries, políticas de segurança em nível de linha, seis tipos de índice etc.
- Se você precisa desses recursos, ótimo, mas a maioria não precisa
- E se você não quer esses recursos do Postgres, há um custo de responsabilidade
- Mesmo sem usar várias contas, ainda precisa configurar autenticação baseada em host e abrir firewall
- Mais funcionalidades significam mais documentação, então fica mais difícil entender o software que você realmente está operando
- A documentação do PostgreSQL 14 tem quase 3 mil páginas
- O SQLite tem um subconjunto dos recursos do Postgres. Mas, no geral, isso cobre 99,9% do que eu quero
- Excelente suporte a SQL, window functions, CTE, busca full-text, suporte a JSON etc.
- E, quando falta alguma funcionalidade, como os dados estão ao lado da minha aplicação, o overhead de trazê-los e processá-los é pequeno
- Enquanto isso, os problemas realmente complexos que precisamos resolver não são solucionados pelos recursos centrais do banco
- Em vez disso, o que eu quero otimizar é só latência e experiência do desenvolvedor
- Então, um grande motivo para considerar SQLite com seriedade é que ele é realmente simples de operar
- Você pode parar de gastar tempo desenhando a camada de banco de dados e simplesmente focar em escrever código de aplicação
- Mas existe outro problema
A luz é lenta demais: The Light is Too Damn Slow
- Estamos começando a bater em limites teóricos. No vácuo, a luz percorre 186 milhas em 1 milissegundo (a distância de ida e volta entre Filadélfia e Nova York)
- Quando você adiciona switches de rede, firewalls e camadas de protocolo de aplicação, fica ainda mais lento
- Dentro de uma única região da AWS, o overhead de latência de uma query ao Postgres pode chegar perto de 1 milissegundo
- Isso não significa que o Postgres é lento; significa que chegamos ao limite da velocidade de movimentação dos dados
- Aplicações modernas processam requisições HTTP e, antes mesmo de executar várias queries de banco e a lógica de negócio ou renderização, já gastam 10 ms
- Existe um número mágico para latência de aplicações: respostas abaixo de 100 ms parecem praticamente instantâneas
- Aplicações rápidas e responsivas deixam os usuários felizes
- 100 ms parece muito, mas é fácil gastar isso sem perceber
- Esse limiar de 100 ms é tão importante que as pessoas pré-renderizam páginas e colocam tudo em CDN para reduzir latência
- É melhor trazer os dados para perto da aplicação. Quão perto? Muito perto mesmo
- O SQLite não está só na mesma máquina da sua aplicação; ele está dentro do processo da sua aplicação
- Quando os dados ficam ao lado da aplicação, a latência por query pode cair para 10~20 microssegundos (μ)
- Ou seja, 50 a 100 vezes mais rápido do que uma query ao Postgres na mesma região
- Mas tem mais: removemos de forma eficiente a latência por query. Nossa aplicação fica mais rápida e também mais simples
- Dá para dividir queries grandes em queries menores e mais fáceis de gerenciar, e gastar mais tempo criando recursos novos em vez de caçar padrões N+1
- Minimizar latência não é importante só em produção. Testar integrações com bancos cliente/servidor tradicionais costuma facilmente crescer até levar minutos no ambiente local, e o sofrimento continua mesmo quando você manda para o CI
- Reduzir o loop de feedback entre mudar o código e terminar os testes economiza tempo e ajuda a manter o foco durante o desenvolvimento
- No SQLite, uma mudança de uma linha pode rodar em memória e executar testes de integração em poucos segundos
Pequeno, rápido, confiável, distribuído globalmente: escolha 4
- O Litestream é distribuído, replicado e, mais importante de tudo, fácil de entender
- Sério, “experimente uma vez”. Não há muita coisa para aprender
- Meu argumento é o seguinte:
- Se construirmos uma replicação confiável e fácil de usar para SQLite, operar aplicações full stack inteiras só com SQLite se torna algo muito atraente
- Na época em que se escreviam tutoriais do tipo “como fazer um blog em Rails”, essa opção foi ignorada, mas o SQLite de hoje aguenta a carga de escrita da maioria das aplicações e pode distribuir leitura entre várias instâncias por meio de réplicas
- O Litestream também tem limitações
- Como foi feito para aplicações single-node, não funciona bem em plataformas serverless nem em deploys rolling
- Como todas as mudanças precisam ser restauradas em sequência, restaurar o banco pode levar alguns minutos
- Estamos preparando a replicação em tempo real, mas o modelo em processo separado traz limitações no controle detalhado das garantias de replicação
- Dá para fazer melhor
- O que venho fazendo no último ano foi definir o núcleo do Litestream e focar na correção
- Estou satisfeito com o ponto a que chegamos agora
- Começou como uma ferramenta simples de backup por streaming, mas está evoluindo gradualmente para um banco de dados distribuído e confiável
- Meu trabalho na Fly.io é tornar isso mais rápido e mais fluido
- Independentemente da Fly.io, mais melhorias ainda serão adicionadas ao Litestream
- O Litestream ganhou um novo lar na Fly.io, mas continuará sendo um projeto open source
- Meu plano para os próximos anos é torná-lo mais útil, independentemente de onde a aplicação esteja rodando, e ver até onde o modelo do SQLite consegue ir
6 comentários
Fiquei com vontade de ler com atenção mais uma vez.
Já pensei em algo parecido, mas isso aqui é muito mais aprofundado e sério. Fiquei impressionado enquanto lia. Também fiquei com vontade de experimentar o Litestream.
Seria ainda melhor se desse para fazer consultas remotamente... buá buá
Isso me lembra o momento em que Elixir está ganhando destaque. É uma ferramenta que oferece banco de dados distribuído embarcado e orquestração no nível da linguagem, mas não sei bem se isso é o futuro.
Li com prazer!
Eu ia só ler rapidamente e resumir, mas acabei me divertindo no processo e o texto ficou mais longo.
Litestream - ferramenta de replicação por streaming do SQLite
Acho que vale a pena ver junto com a pergunta Alguém já usou SQLite como banco de dados primário?.
Também parece haver uma ligação com Cloudflare lança o D1, banco de dados SQL para Workers, divulgado há alguns dias.
Veja também os comentários no HN: https://news.ycombinator.com/item?id=31318708