Tuesday, February 22, 2011

A consistência dos dados no Cassandra

No post anterior, falei sobre o teorema CAP o qual diz que em um sistema distribuído não se pode ter simultâneamente as propriedades de consistência, particionamento e disponibilidade de dados. Neste post vou me aprofundar um pouco mais nesse teorema utilizando o Cassandra, um dos sistemas de armazenamento distribuído mais populares do momento, como exemplo.

De maneira geral, o Cassandra é classificado como um sistema AP (availability and partition), isto é, que  prioriza a disponibilidade e a tolerância a partições na rede em detrimento à consistência de dados. Isso quer dizer que os dados armazenados no Cassandra estarão inconsistentes? A resposta é um sonoro PROVAVELMENTE NÃO! Na literatura, essa resposta leva um nome mais bonito. Ela pode ser chamada, por exemplo, de eventual consistency ou weak consistency e é aí que se encontra a graça do Cassandra.

O Cassandra foi projetado para ser altamente escalável podendo assim gerenciar uma grande quantidade de dados. Imagine, por exemplo, que esse sistema é utilizado para gerenciar os dados postados em um serviço de microblog. Esse microblog tem milhões de usuários que podem fazer buscas por tópicos de interesse. Toda vez que alguém postar algo sobre um tópico x, as pessoas interessadas nesse tópico poderão ler o que foi postado sobre x. Agora imagine se toda vez que alguém requisitar as últimas notícias de x o sistema bloquear as escritas com esse tópico a fim de evitar conflito de dados. O resultado seria que a latência em uma requisição seria tão grande que inviabilizaria o uso do sistema, tornando, pode-se dizer, os dados de x indisponíveis.

Bancos de dados relacionais prezam pela forte consistência de dados e por isso não escalam bem ao gerenciar grande quantidade de dados. Por isso, sistemas ditos NoSQL como o Cassandra defendem a idéia de que muitas aplicações se beneficiam mais de consultas rápidas e com uma eventual inconsistência do que de consultas consistentes mas que não podem envolver um grande número de clientes acessando os dados simultâneamente. O Cassandra, por exemplo, apresenta um trade-off o qual permite ao gerenciador do sistema configurar o nível de consistência desejado. Quanto maior o nível de consistência, maior a latência para operações de leitura/escrita. Ou seja, no fundo, no fundo, é uma questão de escolher entre a consistência das consultas ou o desempenho das mesmas. Desempenho... Estava demorando para essa palavra aparecer, não é?


Configurando a consistência de dados no Cassandra

Para garantir tolerância a falhas, os dados no Cassandra precisam ser replicados. Considerando que um keyspace do Cassandra esteja configurado para conter N réplicas de cada valor associado a uma chave e R seja o número de réplicas lidas e W o número de réplicas escritas, então a consistência é garantida se:

R + W > N

Onde R e W podem assumir, entre outras configurações mais específicas, os valores:

ONE (1) - Na escrita, garante que o dado foi escrito em um commit log e uma tabela de memória de ao menos uma réplica antes de responder ao cliente. Na leitura, o dado será retornado a partir do primeiro nó onde a chave buscada foi encontrada. Essa prática pode resultar em dados antigos sendo retornados, porém, como cada leitura gera uma verificação de consistência em background, consultas subsequentes retornarão o valor correto do dado;

QUORUM (Q) - Na escrita garante que o dado foi escrito em N/2+1 réplicas. Na escrita retorna o valor mais recente lido de N/2+1 réplicas. As réplicas restantes são sincronizadas em background;

ALL (N) - Garante que operações de leitura e escrita envolverão todas as réplicas. Assim, qualquer nó que não responda às consultas fará as operações falharem. 

Possíveis configurações para obter dados consistentes são:
W=1, R=N
W=N, R=1
W=R=Q



Nível de Consistência Padrão

De acordo com o cliente que se utiliza para acessar os dados do Cassandra, o nível de consistência adotado pode mudar. Assim, a fim de evitar surpresas, é sempre aconselhável configurar qual nível de consistência utilizar. No cliente que uso, o Hector, essa configuração é feita no método createKeyspace da Classe HFactory, informando-se a classe que implementa a política de consistência. Tal classe deve implementar a interface ConsistencyLevelPolicy.

A seguir, apresento um exemplo no qual operações de leitura são feitas acessando apenas uma réplica, enquanto operações de escrita (tipicamente mais rápidas no Cassandra) são feitas em N réplicas.

public final class MyWebConsistencyLevel implements ConsistencyLevelPolicy {

    @Override
    public HConsistencyLevel get(OperationType op) {
        switch (op){
          case READ:return HConsistencyLevel.ONE;
          case WRITE: return HConsistencyLevel.ALL;
          default: return HConsistencyLevel.QUORUM; //Just in Case
       }
    }

    @Override
    public HConsistencyLevel get(OperationType op, String cfName) {
        switch (op){
          case READ:return HConsistencyLevel.ONE;
          case WRITE: return HConsistencyLevel.ALL;
          default: return HConsistencyLevel.QUORUM; //Just in Case
       }
    }
}

Esse exemplo funciona com a versão 0.7 do Cassandra e do Hector. 

No comments:

Post a Comment