32 pontos por xguru 2022-05-16 | 6 comentários | Compartilhar no WhatsApp
  • 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
Publicidade
  • 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
Publicidade
  • 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
Publicidade
  • 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
Publicidade

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

 
nicewook 2022-09-27

Fiquei com vontade de ler com atenção mais uma vez.

 
johngrib 2022-05-17

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.

 
japansea 2022-05-16

Seria ainda melhor se desse para fazer consultas remotamente... buá buá

 
mrchypark 2022-05-16

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.

 
nicewook 2022-05-16

Li com prazer!

 
xguru 2022-05-16

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