Artigo no estilo: Curso

Por que eu devo ler este artigo:Este artigo será útil para profissionais que trabalham com aplicações Java EE e desejam incorporar ao seu pool de tecnologias um banco de dados NoSQL para lidar com as crescentes demandas por performance: o Apache Cassandra.

O artigo irá apresentar uma visão geral do Apache Cassandra incluindo conceitos chave, modelo de dados, constraints, ferramentas, boas práticas, entre outros. Além disso, irá demonstrar através da implementação de uma aplicação simples como combinar esse banco de dados com as tecnologias do Java EE, e para isso, será utilizado o driver da DataStax, WildFly 9, PrimeFaces 5.3, Cassandra 2.2, além de outras tecnologias.

Ao longo das últimas décadas muitas coisas aconteceram no mundo da tecnologia: mudanças em linguagens de programação, novas arquiteturas, diferentes metodologias de desenvolvimento, entre outros. No entanto, uma coisa permanecia intacta: bancos de dados relacionais eram a escolha padrão para armazenar dados. Com o crescimento acelerado da Internet e a necessidade cada vez mais comum de manipular altos volumes de dados, isso mudou um pouco. Uma nova tecnologia emergiu e vem se consolidando nos últimos anos: são os chamados bancos de dados NoSQL.

Um dos principais problemas dos bancos de dados relacionais para lidar com grandes massas de dados é o fato de que sua arquitetura cria dificuldades para que esses bancos rodem em cluster. Dessa forma, quando surge a necessidade de escalar as alternativas normalmente são:

· Escalabilidade Vertical: consiste em aumentar os recursos do servidor (memória, CPU, disco e etc.). Além de ter um limite máximo real, normalmente tem custos proibitivos;

· Sharding: essa técnica divide os dados da aplicação em mais de um servidor, distribuindo melhor a carga. O problema é que traz uma enorme complexidade para a aplicação, perde as melhores vantagens dos bancos relacionais, como integridade referencial, e continua tendo um ponto único de falha;

· Master-Slave: um servidor (Master) recebe todas as escritas e replica para as demais instâncias (Slaves), as quais podem atender apenas requisições de leitura. Apesar de poder distribuir melhor a carga entre vários servidores, continua tendo um ponto único de falha e não consegue ter escalabilidade nas operações de escrita por ter apenas um servidor atendendo esse tipo de requisição. Ademais, pode acarretar em custos que inviabilizam sua adoção.

Por esse motivo os bancos de dados NoSQL vêm se popularizando cada vez mais. Executar em cluster com naturalidade, ter alta disponibilidade, facilidade de rodar na nuvem são aspectos comuns nesses novos bancos, pois nasceram justamente para resolver esse tipo de problema. Nesse contexto, o Apache Cassandra se destaca por possuir um modelo arquitetural que proporciona todas essas funcionalidades de uma maneira que minimiza a complexidade existente nesse tipo de ambiente.

Assim, nas próximas seções este artigo irá apresentar o Apache Cassandra de maneira mais detalhada, com o intuito de proporcionar ao leitor um embasamento teórico para o melhor entendimento da tecnologia, bem como irá demonstrar por meio de um exemplo prático algumas das funcionalidades explicadas. Além disso, também será exposta uma abordagem para usá-lo em conjunto com a plataforma Java EE. E para tornar o exemplo mais próximo do nosso dia a dia, outras tecnologias serão usadas, como o PrimeFaces e seus novos recursos de responsividade, CDI e DeltaSpike.

Conhecendo o Apache Cassandra

O Cassandra é um banco de dados NoSQL orientado a colunas desenvolvido em Java. Criado pelo Facebook e depois doado para a Fundação Apache, hoje é reconhecido na indústria de software como um banco de dados massivamente escalável, de alta disponibilidade, distribuído, dentre outras características essenciais para suportar volumes de dados colossais, com crescimento exponencial e carga excessiva de requisições.

Antes de analisar outros detalhes, a seguir serão apresentados alguns conceitos importantes para facilitar o entendimento do restante do artigo:

· Cluster: consiste num grupo de máquinas (nós) onde os dados são distribuídos e armazenados. Pode ser composto de um único nó (single-node cluster) ou vários nós em diversos data centers. A Figura 1 apresenta um exemplo;

· Data center: uma subdivisão dos nós do cluster, os quais estão ligados para propósitos de segregação de replicação e carga. Por exemplo, é possível configurar o Cassandra para replicar dados apenas entre nós do mesmo data center, o que normalmente envolve menos latência do que replicar através de múltiplos data centers. Não se trata necessariamente de um data center físico;

· Nó: uma máquina que faz parte do cluster e que consequentemente armazena dados da base;

· Keyspace: tem o conceito similar a um database no PostgreSQL, onde tabelas são agrupadas para uma finalidade específica. Normalmente para separar dados de aplicações diferentes;

· Família de colunas (Column-Family): nas versões mais atuais do Cassandra esse termo foi substituído por Tabela. Trata-se de um conjunto de pares chave/valor (nome da coluna/valor da coluna) onde são armazenadas as informações da base. Pode-se dizer que com o CQL3 (Cassandra Query Language) esse termo ficou obsoleto.

Representação de um cluster multi-data center

Figura 1. Representação de um cluster multi-data center – Adaptado de DataStax.

CQL – O SQL do Cassandra

O CQL (Cassandra Query Language), como o próprio nome já diz, é a linguagem de consulta para o Cassandra. Atualmente na versão 3.3, é a interface primária para estabelecer comunicação com essa base de dados. Além disso, por possuir muitos aspectos similares ao SQL, não se restringindo apenas ao nome, o CQL3 facilita bastante o aprendizado de profissionais que estão habituados a trabalhar com bancos de dados relacionais. Antes do CQL a interface padrão do Cassandra era a Thrift API (vide BOX 1).

BOX 1. Thrift API

Nos primórdios do Cassandra a única opção disponível para consulta era a Thrift API, uma interface baseada no protocolo RPC e que era bastante burocrática e difícil de entender à primeira vista. Em seguida houve algumas melhoras com o advento do CQL, mas ainda assim muitas características da Thrift API estavam presentes. Somente com o CQL3 o Cassandra pôde ter uma forma de comunicação mais intuitiva, simples e produtiva.

O CQL3 também impactou a forma de modelar dados para o Cassandra. Assim, caso você esteja interessado em se aprofundar no assunto é importante entender que existe uma fase “pré-CQL3” e outra “pós-CQL3”. Isso irá facilitar os estudos e evitará confusão ao aprender dicas e boas práticas diferentes para cada uma dessas fases. Neste artigo, iremos focar no CQL3.

Acessando o CQL

A maneira mais comum de acessar o CQL é através da ferramenta cqlsh, como pode ser observado na Figura 2. Trata-se de um cliente de linha de comando que vem junto com a instalação do Cassandra (CASSANDRA_HOME/bin/cqlsh).

Executando comandos CQL

Figura 2. Executando comandos CQL através do cqlsh.

Caso queira optar por uma ferramenta gráfica, o DataStax DevCenter é uma ótima opção (vide Figura 3). Esta ferramenta é baseada no Eclipse e traz algumas views especializadas para o Cassandra:

· View Connections: Espaço onde você pode gerenciar todas as conexões que criou para algum cluster do Cassandra.

· View Schema: Aqui são listados todos os objetos de uma determinada conexão, o que permite uma visualização hierárquica da estrutura do banco (keyspace > tabela > coluna);

· View CQL Scripts: Através dessa view é possível gerenciar scripts CQL: criar, editar, deletar;

· View Results: Exibe o resultado da última consulta executada; e

· Editor CQL: Editor que possibilita escrever e executar comandos CQL, faz destaque de palavras reservadas, tem code completion e ainda possibilita escolher a conexão onde o comando será executado para cada arquivo aberto.

Escolher entre uma ferramenta e outra normalmente é uma questão de preferência. O DevCenter é mais indicado para quem está iniciando devido a diversas facilidades que uma IDE pode proporcionar, como: wizards para criação de conexões, keyspaces e tabelas, abas para se trabalhar com múltiplos servidores, gerenciamento de scripts, destaque de palavras reservadas, entre outros. O cqlsh, por sua vez, é mais utilizado por quem tem familiaridade com a linha de comando e não está muito a fim de abrir uma IDE pesada na sua máquina. Como facilidade, o cqlsh tem o recurso de tab completion, bastante útil a quem está acostumado com a “telinha preta”.

Executando comandos CQL através do DevCenter

abrir imagem em nova janela

Figura 3. Executando comandos CQL através do DevCenter.

Modelagem – Desnormalizar é preciso

Quando se fala de modelagem de dados em bancos NoSQL, normalmente temos que deixar de lado quase tudo que é considerado boa prática no mundo relacional. No Cassandra não é diferente, e mais, várias das boas práticas da modelagem relacional são consideradas anti-padrões.

Nesse banco a normalização dos dados é considerada um destruidor de performance. Portanto, quase sempre é melhor desnormalizar para escalar a base. Por isso, não se preocupe tanto com a repetição dos dados ou com a quantidade maior de escritas que isso provoca. O Cassandra sabe lidar muito bem com essa situação.

Outra diferença é que nos bancos relacionais o foco da modelagem são as tabelas (entidades), onde a partir das mesmas diversos relacionamentos são obtidos através de joins e chaves estrangeiras. Já as boas práticas do Cassandra instruem a guiar sua modelagem baseado nas consultas. Assim, um padrão recomendado é ter uma tabela por consulta. Por exemplo, se sua aplicação precisa consultar Usuários por nome e por login, serão criadas duas tabelas: usuario_por_nome e usuario_por_login, ambas com os mesmos dados (desnormalização).

O problema da normalização e do foco em entidades é que essas técnicas acabam distribuindo os dados de forma inadequada, e para um banco como o Cassandra, isso pode significar ter que consultar vários nós do cluster para encontrar a informação, o que pode acarretar um grande problema de desempenho. As recomendações apresentadas visam minimizar ao máximo o acesso a múltiplos nós.

Partition Key

Todas as tabelas do Cassandra precisam definir uma chave denominada Partition Key. Esta tem como principal utilidade determinar em qual nó do cluster um dado será armazenado e trata-se de um conceito fundamental a todos que lidam com essa base de dados.

No que se refere à modelagem, a partition key tem relação direta com os filtros de uma consulta. Isso porque no Cassandra qualquer query precisa filtrar a tabela no mínimo pelas colunas que compõem sua partition key. A lógica dessa restrição é que sem a partition key o Cassandra não tem como saber em qual nó a informação está armazenada.

Neste ponto vale ressaltar que não se deve confundir partition key com primary key. Por exemplo, digamos que uma tabela definiu sua chave primária da seguinte forma:

PRIMARY KEY (user_login, status, book_isbn)

Nesse cenário, a primary key é composta pelas colunas user_login, status e book_isbn, enquanto a partition key se resume à primeira coluna, que no caso é user_login. As demais são conhecidas como clustering columns.

Clustering Column

Outro importante aspecto do Cassandra são as Clustering Columns. Essas colunas fazem parte da primary key, mas não da partition key. No exemplo apresentado anteriormente, as colunas status e book_isbn seriam as clustering columns.

A função dessas colunas é determinar a ordenação pela qual os dados serão organizados no disco para uma determinada partition key. É como uma ordenação padrão. Assim, no exemplo supracitado, uma vez que a tabela foi filtrada pela coluna user_login (partition key), os dados apresentados estarão ordenados pelas colunas status e book_isbn, mesmo que não se use um ORDER BY. Essa ordenação padrão ainda pode ser definida na criação da tabela como ASC ou DESC para cada uma das clustering columns. Como essa ordenação já é garantida no momento de armazenar a informação no disco, existe um enorme ganho de desempenho, pois o banco de dados não precisa fazer isso em memória para cada consulta.

Vale informar que o Cassandra só aceita ordenação (ORDER BY) baseado nas clustering columns. Deste modo, a ordenação dos dados para uma consulta pode modificar a forma como modelamos as tabelas. Isto porque o nosso objetivo deve ser executar o SELECT sem precisar especificar uma cláusula ORDER BY e mesmo assim sempre obter os dados na ordem desejada.

Outra maneira que as clustering columns podem afetar a modelagem é que essa ordenação padrão também irá trazer ótima performance nos filtros por intervalos, por exemplo, um período de data. Assim, se você perceber que sua consulta irá precisar desse tipo de filtragem, é fundamental escolher bem as clustering columns.

Distribuição – A chave para a escalabilidade horizontal

Um dos pontos fortes do Cassandra é a sua arquitetura distribuída com suporte a diversas configurações e tamanhos de cluster, desde um único nó a até centenas de nós, como acontece em algumas empresas como eBay, Netflix e Apple.

Essa distribuição é feita de forma automática, não necessitando que desenvolvedores e arquitetos se preocupem em implementar algum tipo de sharding via aplicação. Um componente conhecido como partitioner é o responsável por essa tarefa.

Um partitioner basicamente é uma função que gera um token (hash) a partir da partition key e então distribui os dados entre os nós do cluster baseado nesse token de maneira uniforme e transparente para o desenvolvedor. Em outras palavras, cada nó é responsável por um range compreendido pelo token.

O Cassandra oferece algumas opções de particionadores, sendo o Murmur3Partitioner a opção padrão e mais recomendada. Esse particionador gera tokens de 64 bits que englobam um range de -263 a 263-1. Para mais detalhes, acesse Apache Cassandra Product Guide (veja o endereço na seção Links).

Exemplo de distribuição de dados

Para exemplificar o funcionamento do mecanismo de distribuição de dados, vamos supor que exista uma tabela chamada pessoas_por_nome, que tem como partition key o campo Nome. Dessa forma, essa coluna será usada pelo particionador para gerar os hashes sempre que uma nova linha for inserida na tabela. Para ilustrar melhor essa situação, apresentamos na Tabela 1 alguns valores para a coluna Nome e os seus respectivos hashes, que foram gerados por um partitioner hipotético.

Partition Key

Hash

José

-2245462676723223822

Maria

7723358927203680754

João

-6723372854036780875

Isabel

1168604627387940318

Tabela 1. Partition keys e seus respectivos hashes.

Agora, imagine que o cluster dessa aplicação é composto por quatro máquinas, como exemplificado na Figura 4, e que cada um dos nós é responsável por armazenar os dados de um determinado range do hash gerado pelo partitioner. Por exemplo, nesse cluster o nó C armazenará as linhas que tenham um hash de 0 a 46116860118427387903.

Nós do cluster e seus respectivos ranges

Figura 4. Nós do cluster e seus respectivos ranges - Adaptado de: DataStax.

Considerando o conjunto de dados da Tabela 1 sendo inseridos num cluster com a configuração da Figura 4, teríamos uma distribuição dos dados conforme a Tabela 2. Assim, a linha que tem a coluna Nome igual a João seria armazenada pelo nó A, pois o partitioner gerou um hash para essa partition key o qual fica dentro do intervalo da máquina A.

Início range

Fim range

Partition Key

Hash

A

-9223372036854775808

-4611686018427387903

João

-6723372854036780875

B

-4611686018427387904

-1

José

-2245462676723223822

C

0

4611686018427387903

Isabel

1168604627387940318

D

4611686018427387904

9223372036854775807

Maria

7723358927 ...

Quer ler esse conteúdo completo? Tenha acesso completo