Annie Ruygt
Meu nome é Ben Johnson e trabalho no Litestream na Fly.io. Litestream é o sistema de backup/restauração ausente para SQLite. É um software gratuito e de código aberto que deve ser executado em qualquer lugar e você pode ler mais sobre isso aqui.
Cada vez que escrevemos sobre isso, ficamos um pouco melhores em descrever o que é Litestream. Aí vai: Litestream é uma ferramenta Unix-y para manter um banco de dados SQLite sincronizado com armazenamento de objetos estilo S3. É uma maneira de obter os ganhos de velocidade e simplicidade do SQLite sem se expor a perdas catastróficas de dados. Seu aplicativo nem precisa necessariamente saber que está lá; você pode simplesmente executá-lo como uma ferramenta em segundo plano.
Foram algumas semanas ocupadas!
Recentemente revelamos Sprites. Se você não sabe o que são Sprites, basta dar uma olhada neles. Eles são uma das coisas mais legais que já enviamos. Não vou perder mais tempo vendendo-os para você. Simplesmente, Sprites são um grande negócio, então é um grande negócio para mim que o Litestream seja um componente de suporte para eles.
Sprites dependem diretamente do Litestream de duas maneiras principais.
Primeiro, Litestream SQLite é o núcleo do nosso orquestrador global de Sprites. Ao contrário do nosso principal produto Fly Machines, que depende de um cluster Postgres centralizado, nosso orquestrador Elixir Sprites é executado diretamente no armazenamento de objetos compatível com S3. Cada organização inscrita no Sprites obtém seu próprio banco de dados SQLite, sincronizado pelo Litestream.
Este é um design divertido. Ele aproveita o padrão “muitos bancos de dados SQLite”, que é subestimado. Possui ótimas características de escala. Manter o cluster Postgres feliz à medida que o Fly.io crescia tem sido um grande desafio de engenharia.
Mas no que diz respeito ao Litestream, o orquestrador é chato, e isso é tudo que tenho a dizer sobre isso. A segunda forma como os Sprites usam o Litestream é muito mais interessante.
Litestream é integrado diretamente na pilha de armazenamento em disco executada em cada Sprite.
Os Sprites são iniciados em menos de um segundo e cada um deles inicializa com 100 GB de armazenamento durável. Isso é um pouco complicado de engenharia. Podemos fazer isso porque a raiz do armazenamento para Sprites é o armazenamento de objetos compatível com S3, e podemos torná-lo rápido mantendo um banco de dados de blocos de armazenamento em uso que aproveita o NVMe anexado como um cache de leitura. O sistema que faz isso é o JuiceFS, e o banco de dados — vamos chamá-lo de “mapa de blocos” — é um armazenamento de metadados reescrito, baseado (você adivinhou) no BoltDB.
Eu garoto! É Litestream SQLite, claro.
O armazenamento de Sprite é complicado
Tudo em um Sprite é projetado para surgir rapidamente.
Se a Fly Machine embaixo de um Sprite saltar, poderemos precisar reconstituir o mapa de blocos do armazenamento de objetos. Os mapas de blocos não são enormes, mas não são minúsculos; talvez poucas dezenas de megabytes no pior caso.
O problema é que isso está acontecendo enquanto o Sprite é reinicializado. Para colocar isso em perspectiva, isso é algo que pode acontecer em resposta a uma solicitação da web recebida; isto é, temos que terminar rápido o suficiente para gerar uma resposta oportuna a essa solicitação. O orçamento de tempo é pequeno.
Para tornar isso ainda mais rápido, estamos integrando o Litestream VFS para melhorar os tempos de início. O VFS é uma biblioteca dinâmica que você carrega em seu aplicativo. Depois de fazer isso, você pode fazer coisas assim:
sqlite> .open file:///my.db?vfs=litestream
sqlite> PRAGMA litestream_time = '5 minutes ago';
sqlite> SELECT * FROM sandwich_ratings ORDER BY RANDOM() LIMIT 3 ;
22|Veggie Delight|New York|4
30|Meatball|Los Angeles|5
168|Chicken Shawarma Wrap|Detroit|5
Litestream VFS nos permite executar consultas SQLite pontuais em blobs de armazenamento de objetos, respondendo a consultas antes de baixarmos o banco de dados.
Isso é bom, mas não é perfeito. Tivemos dois problemas:
- Só podíamos ler, não escrever. As pessoas escrevem em discos Sprite. A pilha de armazenamento precisa ser gravada imediatamente.
- Executar uma consulta no armazenamento de objetos é uma dádiva de Deus em uma inicialização a frio, onde não temos outra alternativa além de baixar todo o banco de dados, mas não é rápido o suficiente para o estado estacionário.
Esses são problemas divertidos. Aqui está nossa primeira tentativa de resolvê-los.
VFS gravável
A primeira coisa que fizemos foi tornar o VFS opcionalmente de leitura e gravação. Esse recurso é bastante sutil; é interessante, mas não é tão geral quanto pode parecer. Deixe-me explicar como funciona e depois explicar por que funciona dessa maneira.
Tenha em mente, ao ler isto, que se trata especificamente do VFS. Obviamente, bancos de dados SQLite normais que usam Litestream da maneira normal são graváveis.
O VFS funciona mantendo um índice de (file,offset, size) para cada página do banco de dados no armazenamento de objetos; os dados que compõem o índice são armazenados, em arquivos LTX, para que seja eficiente reconstituí-lo rapidamente quando o VFS for iniciado e as pesquisas forem fortemente armazenadas em cache. Quando questionamos sandwich_ratings anteriormente, nossa biblioteca VFS interceptou o método de leitura SQLite, procurou a página solicitada no índice, buscou-a e armazenou-a em cache.
Isso funciona muito bem para leituras. Escrever é mais difícil.
Nos bastidores, no modo somente leitura, o Litestream faz pesquisas, para que possamos detectar novos arquivos LTX criados por gravadores remotos no banco de dados. Isso oferece suporte a um caso de uso útil em que estamos executando testes ou fazendo consultas analíticas lentas de bancos de dados que precisam permanecer rápidos na produção.
No modo de gravação, não permitimos vários escritores, porque os bancos de dados SQLite distribuídos por vários escritores são a Configuração do Lamento e não somos exploradores de grandes perspectivas de dor. Portanto, o VFS no modo de gravação desativa a pesquisa. Assumimos um único gravador e nenhum backup adicional para monitorar.
Em seguida, nós armazenamos em buffer. As gravações vão para um buffer temporário local (“o buffer de gravação”). A cada segundo ou mais (ou no desligamento limpo), sincronizamos o buffer de gravação com o armazenamento de objetos. Nada escrito através do VFS é verdadeiramente durável até que a sincronização aconteça.
A maioria dos mapas de blocos de armazenamento são muito menores que isso, mas ainda assim.
Agora, lembre-se do caso de uso que pretendemos oferecer suporte aqui. Um Sprite é inicializado a frio e sua pilha de armazenamento precisa servir gravações, milissegundos após a inicialização, sem ter uma cópia completa do mapa de blocos de 10 MB. Este modo VFS gravável nos permite fazer isso.
Criticamente, oferecemos suporte a esse caso de uso apenas até os mesmos requisitos de durabilidade que um Sprite já possui. Todo o armazenamento em um Sprite compartilha essa propriedade de “durabilidade eventual”, portanto os termos da gravação VFS fazem sentido aqui. Eles provavelmente não fazem sentido para o seu aplicativo. Mas se por algum motivo o fizerem, vá em frente! Para habilitar gravações com Litestream VFS, basta definir o LITESTREAM_WRITE_ENABLED variável de ambiente "true".
Hidratação
A pilha de armazenamento Sprite usa SQLite no modo VFS. Em nosso design VFS original, a maioria dos dados é mantida no S3. Novamente: bom na partida a frio, não tão bom no estado estacionário.
Para resolver esse problema, roubamos um truque de sistemas como dm-clone: hidratação de fundo. Em projetos de hidratação, atendemos consultas remotamente enquanto executamos um loop para extrair todo o banco de dados. Quando você inicia o VFS com o LITESTREAM_HYDRATION_PATH conjunto de variáveis de ambiente, iremos hidratar esse arquivo.
A hidratação aproveita a compactação LTX, escrevendo apenas as versões mais recentes de cada página. As leituras não bloqueiam a hidratação; nós os servimos imediatamente no armazenamento de objetos e passamos para o arquivo de hidratação quando estiver pronto.
Quanto ao arquivo de hidratação? É simplesmente uma cópia completa do seu banco de dados. É a mesma coisa que você ganha se correr litestream restore.
Como foi projetado para ambientes como Sprites, que saltam muito, gravamos o banco de dados em um arquivo temporário. Não podemos confiar que o banco de dados esteja usando o estado mais recente toda vez que inicializamos, não sem fazer uma restauração completa, então apenas descartamos o arquivo de hidratação quando saímos do VFS. Esse comportamento está incorporado ao VFS agora. Esse recurso tem o que os Sprites precisam, mas, novamente, talvez não seja o que seu aplicativo deseja.
Juntando tudo
Esta é uma postagem sobre duas mudanças relativamente grandes que fizemos com nosso projeto Litestream de código aberto, mas os recursos têm um escopo restrito para problemas que se parecem com os que nossa pilha de armazenamento precisa. Se você acha que pode usá-los, estou emocionado e espero que você me conte sobre isso.
Para cargas de trabalho comuns de leitura/gravação, você não precisa de nenhum desses mecanismos. Litestream funciona bem sem o VFS, com aplicativos não modificados, apenas rodando como um sidecar junto com seu aplicativo. O objetivo dessa configuração é acompanhar com eficiência as gravações; isso é fácil quando você sabe que tem todo o banco de dados para trabalhar quando as gravações acontecem.
Mas tudo isso é, para mim, um estudo de caso valioso sobre como o Litestream pode ser usado em um domínio de problema relativamente complicado e exigente. Sprites são muito legais e é gratificante saber que cada gravação de disco que acontece em um Sprite é executada através do Litestream.
Fonte: theverge

