- A Wafris é uma empresa de firewall de aplicação web open source que fornece um cliente middleware para Rails
- O cliente v1 exigia um armazenamento de dados Redis local, mas na v2 usa SQLite
- Explica o contexto da decisão de migrar de Redis para SQLite, as considerações de desempenho e as mudanças de arquitetura
TL;DR
- O SQLite tem coisas em que é bom e coisas em que não é
- O Redis tem coisas em que é bom e coisas em que não é
- RDBMSs tradicionais (Postgres/MySQL) têm coisas em que são bons e coisas em que não são
- Esses armazenamentos de dados não são intercambiáveis diretamente, e tentar fazer isso traz problemas
- Este texto descreve os testes e o processo de decisão envolvidos na re-arquitetura do cliente v1 baseado em Redis para o cliente v2 baseado em SQLite
Fatores que forçaram a mudança
- O objetivo da Wafris é facilitar para desenvolvedores protegerem seus sites
- Por causa de problemas de implantação do Redis, a v1 não alcançava totalmente esse objetivo
- O Redis foi escolhido porque a equipe trabalhava em ambientes como Heroku, onde era fácil usá-lo, mas muitos usuários enfrentavam problemas de implantação do Redis
- Fazer o usuário usar um banco separado como o Redis não era o melhor para ele
O que é “velocidade”?
- O Redis é “mais rápido” que RDBMSs tradicionais, mas ainda exige gerenciar conexão, memória, processos etc.
- Em ambientes de nuvem, a latência de rede pode ser um grande problema
- Como as regras do Wafris precisam ser avaliadas a cada requisição HTTP recebida, a latência de rede pode deixar a aplicação mais lenta
Suposição de monólito
- Existem aplicações totalmente distribuídas, mas a maioria dos apps Rails são “Majestic Monoliths”
- Apps implantados em várias regiões, divididos em servidores com funções sobrepostas ou apenas parcialmente em Rails têm ainda mais problemas ao usar Redis
Repensando a arquitetura
- O Wafris é um firewall de aplicação web instalado como middleware do Rails
- Simplificando em duas etapas: 1) comparar requisições HTTP com regras e 2) reportar o resultado do processamento
- A “leitura” das regras (etapa 1) é muito mais importante do que a “escrita” (etapa 2)
- As leituras precisam ser processadas em sequência, não podem falhar e afetam o desempenho percebido pelo usuário
- As escritas podem ser mais lentas, feitas em lote ou de forma assíncrona
Entra o SQLite
- Outras pessoas já explicaram bem para quais usos o SQLite é adequado
- O SQLite não compete com bancos cliente/servidor, e sim com
fopen()
- Ao eliminar a ida e volta pela rede, a expectativa era de que ele fosse muito mais rápido que o Redis
- Foi decidido avaliar benchmarks entre SQLite e Redis
Benchmark entre SQLite e Redis
- Fazer benchmark é uma arte obscura de se enganar com números precisos
- Benchmark de armazenamento de dados é ainda mais complicado
- Não se queria encontrar uma velocidade absoluta, e sim criar um benchmark específico para os dados e o caso de uso da equipe
- Tweaks de otimização foram ignorados. A ideia era que o Wafris funcionasse imediatamente ao ser colocado no app
- Em vez de benchmarks teóricos, foram testados o caminho principal do app e a pior consulta
- A pior consulta é uma requisição para uma estrutura de dados complexa de “decimal lexical” que mapeia intervalos de IP (IPv4, IPv6) para categorias
- As buscas por intervalo foram pré-calculadas e gravadas em tabelas SQLite e conjuntos ordenados do Redis
- A cada requisição HTTP recebida, é preciso comparar o IP da requisição com intervalos personalizados de permitir/bloquear, intervalos GeoIP e intervalos de reputação de IP
Protocolo de teste
- Os testes foram feitos em um MacBook Air M2, com Redis instalado via Homebrew e um banco SQLite local
- Os testes usaram um conjunto de dados de intervalos já existente, com 1,2 milhão de itens
- Vários conjuntos de IP foram executados em SQLite e Redis na mesma ordem
- Em cada multiplicador, o teste foi executado 5 vezes e a média foi calculada
Resultado dos testes
- O SQLite venceu o Redis com folga (no caso de uso específico da equipe)
- O SQLite foi cerca de 3 vezes mais rápido que uma instância local do Redis
- Isso antes mesmo de considerar a latência de rede
- Mesmo que o SQLite só empatasse com o Redis, ainda haveria ganho por eliminar o tempo de rede
O que ficou fora do gráfico
- Mesmo que o desempenho do SQLite fosse 2 vezes pior no benchmark, na prática ele ainda poderia ser mais rápido por causa da latência de rede
- Não importa o quão poderoso seja o servidor Redis, ainda existem limites de largura de banda, conexões e latência entre regiões
- O SQLite permite uma escalabilidade horizontal quase infinita “de graça”
- O onboarding com SQLite fica muito melhor. O usuário talvez nem perceba que ele está sendo usado
- Dá para extrair mais desempenho do Redis, mas foi difícil convencer usuários a mudar a configuração do Redis
O resultado é só o começo
- Foi comprovado que o SQLite é mais rápido que o Redis, mas existem trade-offs reais
- Os testes acima não consideram escrita
- Para gerenciar a competição entre leituras e escritas, ainda são necessários conexão com banco, pool de conexões, transações etc.
- Assim como um supercarro elétrico tem dificuldade para carregar blocos de concreto, o SQLite não deve ser usado em funções para as quais não é adequado
Construindo uma arquitetura de sincronização
- Na v1 (Redis), quando o usuário atualizava regras no Wafris Hub, as regras no armazenamento Redis eram atualizadas
- Isso não funciona com SQLite, porque não dá para fazer “push” para o servidor web
- Na v2 (SQLite): 1) o usuário atualiza regras no Wafris Hub 2) em intervalos regulares, o cliente verifica regras atualizadas 3) quando há atualização, baixa um banco SQLite totalmente novo
- Isso reduz bastante a responsabilidade do usuário por instalação e configuração
- A taxa de sucesso na instalação do cliente v2 aumentou 3 vezes
Arquitetura distribuída com SQLite
- Considere um app Rails implantado em um provedor de nuvem com auto scaling ativado
- Se as requisições aumentam de 100 req/s para 10.000 req/s, as instâncias de computação escalam, mas o banco não
- Na prática, esse é o principal motivo de apps Rails caírem por sobrecarga
- Sincronizar o banco SQLite em cada instância de computação resolve isso, porque mantém todas as chamadas locais
E quanto às escritas?
- O app foi dividido em caminhos de leitura (avaliação de regras) e escrita (relatórios), e então o caminho de escrita foi ignorado
- O caminho de escrita foi redesenhado como 1) reportar conectando-se ao Wafris Hub de forma assíncrona 2) enviar relatórios em lote 3) remover completamente escritas em banco do cliente
- Isso pode não funcionar para outras pessoas, mas a equipe só se preocupa com usuários que querem um cliente Wafris rápido e fácil de implantar
Conclusão
- Há grande satisfação com a arquitetura v2 usando SQLite
- Ela já ajuda muitos sites a resistirem a ataques e permanecerem online
- Ficou muito mais fácil começar, reduzindo o trabalho de suporte da equipe e o incômodo para os usuários
- Isso é visto como uma vitória para uma internet mais segura e protegida
7 comentários
O SQLite é bom o bastante, mas, neste caso, dá a impressão de que era simplesmente um caso de uso que não combinava muito com o Redis...
Fazer o benchmark em um M2 é meio...
Então precisa medir tudo isso em cada instância da AWS? Você está esperando demais do open source.
Foi feito no mesmo ambiente de servidor; isso é um problema?
Para benchmark, é necessário usar uma CPU específica...?
Que tipo de problema pode haver no que foi feito no m2? (além do fato de que o ambiente de serviço real não usa um processador m2)
Esse é o problema. Fazer experimentos em laboratório e afirmar: isto é perfeito para uso comercial!