Blog

Comparando sistemas de storage

16 Abr

Sistemas de Storage

Aqui na Zunnit, ao nos depararmos com a necessidade de um sistema eficiente para armazenamento de grandes volumes de dados, optamos pelos bancos não relacionais ou NoSQL (not only SQL). Um banco NoSQL é uma opção natural para sistemas distribuídos e de alta capacidade, já que proveem recursos de escalabilidade horizontal, tolerância a falhas e armazenamento descentralizado. Esses bancos geralmente seguem o teorema CAP, também conhecido como teorema de Brewer. Este teorema apresenta três elementos que influenciam o projeto dos sistemas de armazenamento de dados, que são: Consistência, Disponibilidade e Tolerância a particionamento (Consistency, Availability, Partition tolerance), apresentado por Eric Brewer, em 2000.

Segundo Brewer, não é possível que um sistema distribuído de armazenamento de dados atenda aos 3 requisitos simultaneamente: Sistemas que dependem de consistência forte e tolerância a particionamento abrem mão da alta disponibilidade; já sistemas com alta consistência e alta disponibilidade abrem mão da tolerância a particionamento, ou seja, não conseguem lidar com a falha de uma partição podendo, no pior caso, deixar o sistema inteiro indisponível (algumas configurações dos bancos de dados tradicionais funcionam assim); por fim, os sistemas que garantem alta disponibilidade e tolerância a particionamento abrem mão da consistência forte, sendo que os dados não são sincronizados em tempo real, mas em algum momento, através de processos que rodam em background. Além de privilegiar alta disponibilidade e particionamento, outro ponto que pode se beneficiar do sacrifício da consistência é a latência, ou seja, o tempo de resposta das requisições. Entre os bancos que sacrificam a consistência estão o Cassandra e o Voldemort. E iremos fazer uma comparação entre eles.

Os dois dizem ter escalabilidade linear, ou seja, a adição de novos nós aumenta linearmente (ou quase) a capacidade e desempenho do cluster. Uma característica interessante que faz dos dois uma opção boa e barata para aplicações distribuídas é que eles presumem execução em hardware de baixo custo e pouco confiável que irá invariavelmente falhar. Por isso, são desenvolvidos com o conceito de disponibilidade e tolerância a falhas em mente.

NoSQL

- Escalabilidade horizontal
- Armazenamento distribuído e descentralizado
- Altamente disponível
- Tolerante a falhas
- Alto desempenho

O Cassandra

  • Modelo de dados baseado em colunas
  • Todos os nós são iguais
    • leituras e escritas podem ser feitas em qualquer nó
  • Consistência eventual
  • Armazenamento
    • em memória: memtable
    • persistente: SSTable

O Cassandra é um sistema de armazenamento de dados, baseado em um modelo chave-valor estruturado, altamente escalável, distribuído, que implementa o conceito de consistência eventual. A tecnologia do Cassandra foi baseada no Dynamo, desenvolvido pela Amazon – e que deu origem ao produto DynamoDB, e o modelo de dados é baseado no BigTable, desenvolvido pela Google. Ele foi desenvolvido pela equipe do Facebook, que abriu seu código em 2008.

No Cassandra, todos os nós são iguais, não havendo um ponto único de falha pois não há um ponto central de coordenação e uma requisição de leitura ou escrita pode ser feita a qualquer dos nós. Quando um cliente se conecta a um nó para uma leitura ou escrita, este atua como um coordenador para esta conexão em particular, atuando como uma espécie de proxy entre o cliente e os nós que realmente contém os dados.

O espaço de dados gerido pelo Cassandra pode ser representado por um anel, que é dividido em intervalos iguais entre os nós, sendo cada nó responsável por um ou mais intervalos. A posição de cada nó neste anel, e consequentemente os intervalos pelos quais ele é responsável, é determinada por um identificador, chamado initial token, gerado no momento em que o nó é adicionado ao cluster.

A cada operação de leitura ou escrita o cliente pode decidir o quão consistente esta deve ser. Isto permite à aplicação definir a consistência por operação, variando o tempo de resposta em função da precisão dos dados. A consistência é uma medida que diz se o dado está atualizado e sincronizado em todos os nós que devem contê-lo.

No Cassandra a consistência é eventual, porém ajustável. Operações com consistência alta são mais lentas pois devem esperar por mais respostas, de mais réplicas, para validá-lo. Já as operações com consistência baixa retornam os dados em menos tempo, mas podem retornar dados desatualizados (ou nenhum, caso os nós consultados ainda não tenham sido atualizados).

Níveis de consistência

- ONE – Retorna o dado do primeiro nó que responder
- QUORUM – Requisita o dado a todos os nós que têm uma réplica do dado. Quando a maioria simples dos nós responder, retorna ao cliente o dado com o timestamp mais recente
- ALL – Requisita o dado a todos os nós que têm uma réplica do dado. Espera pela resposta de todos e retorna ao cliente o dado com o timestamp mais recente

O coordenador envia a requisição de escrita a todas as réplicas que devem conter o dado. Os nós vão gravar os dados independentemente do nível de consistência determinado, mas a consistência determina quantos nós devem responder OK para a operação de escrita ser considerada bem sucedida. Os dados mais recentes residem em memória e no commitlog, e eventualmente as memtables são gravadas em disco, em arquivos chamados SSTable.

Existem dois tipos de requisição de leitura: uma requisição direta e uma requisição em background, de reparação. Sempre que há uma requisição de leitura, requisições em background são enviadas a todos os nós que não tenham recebido uma requisição direta, garantindo que o dado requisitado esteja consistente em todas as réplicas. O número de réplicas que recebem a requisição de leitura é determinado pelo nível de consistência. Os nós contatados respondem e, quando mais de um nó responde, os dados são comparados para verificar se está consistente. Se não está, o dado mais recente – baseado no timestamp – é usado. Por isso é fundamental que o relógio do sistema esteja sincronizado em todos os nós do cluster. O importante aqui não é manter o horário correto mas, principalmente, manter os relógios de todos os nós do cluster sincronizados.

O Cassandra tem uma grande base de usuários, como Cisco, Clearspring, CloudKick, Netflix, Formspring, Rackspace, Plaxo, entre outros. Entretanto, o próprio Facebook não o usa mais, tendo construído outra ferramenta baseada no HBase. Ele também conta com uma vasta documentação, incluindo diversos livros dedicados exclusivamente ao Cassandra.

Sua base de usuários contribuiu fortemente para a estabilidade do sistema e aprimoramento dos recursos, além de desenvolver ferramentas para facilitar a administração dos nós. A Datastax provê produtos e serviços baseados no Cassandra comercialmente, e tem uma documentação bem completa e didática do sistema.

Durante 2011, 3 novas versões do Cassandra foram lançadas com significativas melhorias, reduzindo o consumo de memória e aumentando o desempenho das operações de leitura e escrita.

O Voldemort

  • Modelo chave-valor
  • Nós independentes
  • Consistência eventual
  • Implementação em camadas
  • Persistência pode ser feita em diversos sistemas

O Voldemort é baseado em um modelo chave-valor simples, apesar de aceitar objetos complexos tanto nas chaves como nos valores.

O principal objetivo durante seu desenvolvimento foi conseguir baixa latência e alta disponibilidade no acesso aos dados. Ele também foi inspirado no Amazon Dynamo e no Memcached, desenvolvido pela equipe do Linkedin e é usado para resolver alguns problemas de escalabilidade onde um simples particionamento dos dados não é o suficiente. Usando o Voldemort a equipe do Linkedin conseguiu reduzir a latência de algumas operações de 400ms a cerca de 10ms. Seu código foi aberto em 2009.

Assim como o Cassandra, o Voldemort não tem um ponto único de falha, sendo totalmente distribuído e sem um nó coordenador central. As requisições de leitura ou escrita podem ser feitas a qualquer dos nós e será roteada aos nós que contém os dados. Ele foi desenvolvido para ter alto desempenho de leitura, com o objetivo de ser um sistema de armazenamento que não necessita de um sistema de cache por cima.

Sua arquitetura é definida em diversas camadas, cada uma delas com uma interface provendo basicamente as funções put, get e delete. Como o Voldemort implementa um modelo de dados muito simples, as consultas também são extremamente simples – mas muito rápidas. Entretanto, qualquer tipo de lógica mais complexa tem que ser implementada no cliente.

Uma das grandes vantagens do modelo em camadas usado é que elas podem ser combinadas em diferentes ordens e outras camadas podem ser facilmente criadas e adicionadas em diferentes posições na pilha. Entre as principais camadas estão:

- Client API: É a interface com os clientes, que recebe as requisições;

- Resolução de Conflitos: Resolve problemas de consistência durante as operações de leitura/escrita, usando o versionamento dos dados para determinar o mais recente implementando um relógio lógico. Esse versionamento não permite que versões anteriores dos dados possam ser acessadas;

- Serialização: Permite serializar os dados em diversos formatos, entre eles JSON, Protocol Buffers, Thrift e até mesmo formatos personalizados;

- Roteamento: Abstrai uma série de complexidades como a replicação dos dados, recuperação de falhas, etc. É responsável pela distribuição e replicação dos dados entre os nós e pode ser implementada no cliente ou no servidor – ou mesmo uma solução híbrida;

- Consistência: Implementa os mecanismos de reparação e controle da consistência dos dados, sendo os dois principais o read repair, que é um mecanismo online onde requisições em background são disparadas para atualizar os nós com a versão mais recente do dado; e o hinted handoff, usado quando um nó não está disponível e as escritas são feitas em qualquer outro nó e encaminhadas ao nó correto quando este estiver online novamente. O controle de consistência é baseado em uma espécie de versionamento, usando um relógio lógico. Este controle de versão não significa que seja possível recuperar um dado sobrescrito. No caso de escritas concorrentes, todas as versões conflitantes são armazenadas e o conflito é resolvido pelo cliente durante a leitura, ou seja, dependente da implementação. Mas é possível adicionar uma camada para que essa resolução seja feita no servidor;

- Rede: Responsável pela comunicação com o cliente, é implementado como um servidor HTTP ou através de conexões socket, recentemente recebeu a implementação de uma versão não blocante;

- Storage: Responsável por gravar os dados, de fato, em um meio persistente. Permite o uso de diversas engines de armazenamento, sendo o BDB a opção padrão.

A base de usuários do Voldemort não é tão grande quanto a do Cassandra, apesar de ter seu código liberado apenas um ano depois. Entre os usuários estão o próprio Linkedin, eBay, AppScale, eHarmony e Medallia. Ainda não há uma empresa que comercialize serviços baseados no Voldemort e, além disso, ele conta com uma documentação mais pobre, sendo sua maioria encontrada no site do próprio projeto e no repositório Git do projeto.

O benchmark

Dado que as características e os recursos oferecidos pelos dois atendem às nossas necessidades, uma comparação de suma importância é a do desempenho dos dois, em especial nas leituras.

Para os testes foi usado o YCSB. O YCSB é um dos frameworks mais conhecidos para testes de desempenho em bancos NoSQL; ele foi criado pelo Yahoo e suporta vários bancos diferentes.

Os servidores e os clientes utilizados para o teste foram máquinas virtuais de 64 bits, com 8 cores e 7GB de memória. Apesar das recomendações de tunning do Cassandra, que dizem ser o ideal separar em discos físicos diferentes os arquivos de commitlog dos arquivos de dados, os SSTables, estes arquivos ficaram no mesmo disco virtual. Na verdade, em se tratando de máquinas virtuais, não haveria diferenças significativas criar dois discos virtuais diferentes na mesma infraestrutura física para armazenar estes arquivos de acordo com as recomendações. Há ainda outras recomendações sobre a localização desses arquivos quando em instâncias em nuvem.

Foram feitas duas baterias de testes, uma com os clientes rodando apenas uma thread e outra com os clientes rodando 40 threads. Em nenhum momento os servidores ou os clientes ficaram saturados (processador, memória ou disco). Em todas as sessões de testes foram usados dados com 1KB de tamanho e a distribuição de Zipf na escolha dos itens e não foi utilizado nenhum tipo de tunning dos bancos. Neste caso, o tunning teria pouco efeito, já que a maioria das recomendações está baseada no uso de máquinas físicas. O importante, nesse caso, é que não se privilegiou nenhum componente, rodando os testes com as configurações básicas de ambos os bancos, em máquinas idênticas.

Casos de Teste

- 50% read/50% update
- 95% read / 5% update
- 100% read
- 95% read / 5% insert

Resultados

Esse gráfico mostra o tempo médio (em milissegundos) para as operações de leitura e update (escrita) no teste com o cliente rodando só uma thread. Quanto menor, melhor. O tempo de leitura do Voldemort, como era de se esperar, é muito menor que o do Cassandra – que não chega a ser necessariamente alto, mas o tempo de escrita é muito maior.

Este é o resultado do teste com o cliente rodando 40 threads. O tempo de escrita do Voldemort continua bem mais alto que o do Cassandra, mas o interessante é o tempo de leitura ter crescido a ponto de ficar, nos 4 cenários, maior que o do Cassandra. O throughtput do Cassandra também foi bem melhor, realizando bem mais operações por segundo.

Este gráfico mostra o  throughput de operações realizadas, para o caso em que são usadas 40 threads. Aqui, quanto maior melhor. É interessante verificar também o tempo máximo para 99% dos casos:

99% dos casos

  • 50% read/50% update

Voldemort: 5ms
Cassandra: 10ms

  • 95% read/5% update

Voldemort: 6ms
Cassandra: 9ms

  • 100% read

Voldemort: 5ms
Cassandra: 8ms

Aqui também estão apresentados os tempos apenas dos testes com 40 threads.

Conclusão

Não há uma escolha óbvia. O modelo de dados implementado pelo Cassandra dá mais possibilidades, apesar do Voldemort ter uma arquitetura muito interessante, o que pode vir a torná-lo muito versátil.

De maneira geral, o Cassandra apresentou melhor desempenho com carga maior. Além disso, os últimos desenvolvimentos – este ano foram lançadas 3 versões com significativas melhorias – tornaram o desempenho de leitura (que era um dos seus pontos fracos) absurdamente melhor. Porém, existem cenários onde o Voldemort parece ter um desempenho melhor, dependendo do tamanho dos dados e dos recursos disponíveis no servidor.

Analisando o caso de teste 1, o tempo médio de resposta do Voldemort para as operações de leitura em que se utiliza apenas uma thread, é significativamente menor que o do Cassandra. Entretanto, ao aumentar a carga passando para 40 threads, o tempo médio do Voldemort aumenta mais de dez vezes e fica muito atrás do Cassandra.

Outro indicador importante é o throughput, onde o Voldemort chegou a 6.155 ops/s e o Cassandra chegou a 28.185 ops/s, atendendo mais de 4 vezes mais requisições que o Voldemort. Nos outros casos de teste, os resultados mantiveram o mesmo perfil, sendo que neste primeiro caso percebeu-se uma diferença mais significativa no throughput.

No caso médio, o Cassandra teve um desempenho muito superior ao Voldemort. Entretanto, ao analisar as requisições de leitura acima dos 99%, o Voldemort apresentou resultado significativamente melhor. Ou seja, as suas piores requisições de leitura – aquelas que mais demoraram – não chegaram a demorar tanto quanto às do Cassandra, sendo que o caso de teste 1 apresentou o resultado mais significativo: 11 ms para o Cassandra e 5 ms para o Voldemort. É ainda pior ao se analisar o tempo máximo: A requisição mais demorada, onde o Cassandra respondeu em 75s e o Voldemort em 487ms. O Cassandra chegou a ter 1% das requisições com tempo acima de 10s e consideráveis 20% com tempo acima de 2s.

Se a necessidade é o atendimento do maior número de requisições, aceitando o fato de que em situações onde a demanda está próxima ao limite do banco por um longo período algumas requisições poderão demorar bem mais que a média, o Cassandra é a melhor opção. Mas se a necessidade for por um banco que tenha um desempenho mais estável em situações extremas, mesmo não atendendo tantas requisições, o Voldemort é uma boa escolha.

Existem outras opções a se avaliar, tanto em desempenho quanto custos. Um exemplo é o DynamoDB, lançado este ano pela Amazon como parte da sua plataforma AWS e ainda em beta, que evitaria a necessidade de se preocupar com a operação e dimensionamento do hardware/software utilizado pelo NoSQL.