- rqlite é um banco de dados relacional distribuído, leve e de código aberto, escrito em Go e desenvolvido com base em SQLite e Raft
- O desenvolvimento começou em 2014, priorizando confiabilidade e qualidade, e mesmo após mais de 10 anos de desenvolvimento e implantação, foram relatados menos de 10 casos de panic em ambientes de produção
- Testar sistemas distribuídos exige consideração cuidadosa em várias camadas, seguindo a filosofia de manter a qualidade dentro da simplicidade
Pirâmide de testes: uma abordagem eficaz
- Os testes do rqlite seguem a "pirâmide de testes"
- Pirâmide de testes: estrutura baseada em testes unitários, incluindo testes de integração e um número mínimo de testes end-to-end (E2E)
- Isso fornece uma suíte de testes eficiente, fácil de depurar e orientada por objetivos
Testes unitários: o núcleo da qualidade
- Os testes unitários verificam componentes independentes e oferecem um equilíbrio entre velocidade e precisão
- Com base no SQLite e na arquitetura "shared nothing", a maior parte das funcionalidades pode ser coberta por testes unitários
- De todo o código do rqlite (cerca de 75.000 linhas), os testes unitários somam aproximadamente 27.000 linhas
- Os testes são concluídos em poucos minutos, permitindo execuções frequentes durante o desenvolvimento
Testes em nível de sistema: validação do consenso
- Os testes em nível de sistema validam a interação entre o módulo de consenso Raft e o SQLite
- Principais itens testados:
- Replicação de instruções SQLite entre nós
- Operações de leitura em diferentes níveis de consistência
- Validação da recuperação de falhas do cluster e da eleição de líder
- Com cerca de 7.000 linhas de código de teste, cobrem de forma abrangente as interações em configurações de nó único e de múltiplos nós
Testes end-to-end: uma camada mínima
- Os testes end-to-end atuam como smoke tests para verificar inicialização do sistema, formação do cluster e comportamento básico
- Foram escritos em Python e validam funções principais executando um cluster real do rqlite
- Exemplo: validação de backup para AWS S3
- O código de teste é limitado a cerca de 5.000 linhas, adotando uma abordagem restrita para minimizar o custo de depuração
Testes de desempenho: testando os limites
- Os testes de desempenho avaliam métricas como:
- Velocidade máxima de INSERT
- Processamento de consultas simultâneas
- Comparação de uso de memória, CPU e disco
- Incluem testes com bancos de dados SQLite acima de 2GB, analisando gerenciamento de memória e gargalos de escrita em disco
- Identificam problemas de desempenho e garantem estabilidade por meio de otimizações
Lições aprendidas
- Comece a testar desde o início
- Testes unitários são a forma mais eficaz de construir confiança no sistema
- Não se deve adiar a escrita de testes unitários durante o desenvolvimento, pois eles permitem encontrar bugs mais rapidamente do que testes de integração ou E2E
- Mantenha o código de teste simples
- A suíte de testes não é o lugar para insistir em refatorações complexas ou no princípio DRY (Don't Repeat Yourself)
- É importante escrever código fácil de entender, aceitando até mesmo código boilerplate adicional
- Valide os testes
- Ao escrever testes, execute-os novamente invertendo temporariamente o resultado esperado
- Um teste bem escrito deve falhar nesse caso, o que ajuda a evitar erros no próprio código de teste com antecedência
- Não ignore falhas de teste
- Mesmo falhas raras ou difíceis de entender fornecem informações importantes sobre o software
- Casos de falha difíceis de depurar frequentemente podem ser uma oportunidade para descobrir defeitos críticos no código
- Maximize o determinismo
- Construa mecanismos que permitam executar manualmente os processos automáticos do sistema
- Exemplo: a funcionalidade de snapshot do Raft normalmente roda de forma semiautomática, mas foi projetada para poder ser acionada explicitamente durante os testes
- Seja deliberado
- Testes de integração de nível mais alto ou testes E2E só devem ser adicionados quando sua necessidade estiver claramente comprovada
- Testes em excesso podem reduzir a velocidade de desenvolvimento e de depuração
- Aplicar e iterar
- Nos testes de desempenho, as chamadas de fsync foram identificadas como o principal gargalo, e as entradas de log do Raft passaram a ser compactadas antes de serem gravadas em disco para otimizar o uso do disco
- Valorize a eficiência
- Manter uma suíte de testes que roda em poucos minutos permite um ciclo rápido de desenvolvimento iterativo
- Essa é uma vantagem essencial para manter e dar continuidade a um projeto de código aberto
Qualidade em primeiro lugar
- Segue-se a pirâmide de testes, com cada camada projetada para ter um propósito claro
- À medida que a complexidade dos sistemas distribuídos aumenta, manter a simplicidade dos testes é essencial
- O objetivo é construir um banco de dados confiável e fácil de operar
1 comentários
Comentários do Hacker News
O primeiro teste é o mais difícil, mas vale a pena adicioná-lo. Depois disso, os testes ficam mais fáceis
O comprometimento com o projeto é impressionante
A pirâmide de testes faz sentido, mas muitas vezes nem todos os níveis existem, então é preciso melhorar isso rapidamente
Parece haver um erro de copiar e colar no FAQ
Estou ansioso pelo relatório do Jepsen
Também gostei do formato em vídeo
Tenho inveja da configuração de testes de performance
Já usei o rqlite, e ele transmite bem a simplicidade
Pergunta-se a opinião sobre testes de simulação determinística
Há curiosidade sobre o uso do rqlite em produção
O rqlite é um projeto original e genial