Apache Cassandra: confira absolutamente tudo a respeito

Software Architect da Netshoes disseca o banco de dados e passa o que você precisa saber

alexandre sabino
NSTech
12 min readAug 1, 2018

--

Introdução

Neste artigo serão exploradas as principais características do banco de dados Apache Cassandra. Criado originalmente pelo Facebook, sua arquitetura foi inspirada pelo DynamoDB da Amazon e seu modelo de dados foi baseado no BigTable do Google — como open source desde 2008. Atualmente ele é mantido pela fundação Apache.

Bancos NoSQL

O Cassandra é um banco de dados não relacional e colunar. Antes de mais nada é necessário entender um pouco mais sobre esses modelos.
O termo NoSQL surgiu para segmentar os tradicionais bancos relacionais dos novos modelos de armazenamento. No decorrer da história esse acrônimo teve diversos significados, atualmente é “not only SQL”. A ideia não é substituir os bancos relacionais e sim somar outras alternativas de armazenamento se tornando mais uma opção para os desenvolvedores.

Os bancos NoSQL podem ser agrupados em 4 tipos bem distintos:

Chave e valor:
Os dados são persistidos na estrutura Key Value (similar a um Map do java) e só poderão ser recuperados a partir da chave.

Documentos:
Os dados são persistidos na estrutura de JSON, e possuem um schema bem flexível (schemaless).

Grafos:
Os dados são persistidos em um esquema de grafo, onde um registro “aponta” para o próximo. A imagem abaixo deixa um pouco mais claro como funciona esse relacionamento:

Colunar:
Para ficar claro o que é um banco de dados colunar, precisamos relembrar dos bancos relacionais: eles armazenam os dados em linhas (rows) enquanto os bancos colunares persistem os dados por colunas criando um relacionamento através de um id.

Teorema de CAP

Por volta dos anos 2000, na Universidade da Califórnia, Eric Brewer criou o teorema de CAP. O acrônimo tem o seguinte significado:

Consistency: Se a uma determinada aplicação alterar os registros de uma tabela, todos os clients irão ler exatamente a mesma informação.

Availability: Disponibilidade do banco para persistência e leitura.

Partition Tolerance: Os dados devem ser distribuídos em várias máquinas e o banco deve lidar com falhas na rede, resolvendo conflitos de forma transparente para os clients.

Conforme o teorema, os bancos de dados podem atender plenamente apenas 2 desses 3 requisitos citados acima.

O Cassandra é considerado um banco AP (Availability / Partition Tolerance) por ser altamente escalável e distribuído. Como ele não tem transações em determinados momentos, a leitura dos dados pode não ter consistência, tendo em vista que o Cassandra demora alguns milissegundos para reorganizar o cluster.

Arquitetura do Cassandra

O Cassandra é segmentado em keyspaces, tables (column families), rows e columns. Segue abaixo uma visão hierárquica dessas camadas para maior entendimento:

Keyspace

É o equivalente a um banco de dados no mundo relacional e agrupa as tabelas do sistema. Suas principais configurações são:

● Replication factor: O fator de replicação define quantas “cópias” existirão dos dados. Caso tenha um cluster com 5 máquinas e o replication factor igual a 2, o Cassandra irá garantir que seus dados estarão em pelo menos 2 máquinas. Essa feature é bem importante para alta disponibilidade do banco.

● Replica placement strategy: É a estratégia de replicação dos dados, existem 2 estratégias, sendo:

○ SimpleStrategy: Respeitando o fator de replicação o Cassandra simplesmente replica o dado para o próximo nó (node) do cluster.

○ NetworkTopologyStrategy: Com essa estratégia o Cassandra irá persistir os dados em mais de um datacenter (obrigatoriamente o cluster deve estar configurado para ser multi-datacenter).

Tables (Column family)

É o equivalente à uma tabela no mundo relacional. Um keyspace pode conter “N” column families, que por sua vez podem conter “N” rows.

A comparação entre column families e tabelas do banco relacional é apenas para facilitar o entendimento. Vale ressaltar que o comportamento é bem diferente, como por exemplo: JOIN`s não são suportados, com isso será necessário desnormalizar os dados. Falaremos sobre modelagem mais adiante.

Nas columns families são definidos os seguintes atributos:

● Primary key: A primary key é composta de 2 partes:
○ Partition key: Ela define qual partição será armazenado o dado.
○ Clustering key: É o restante da chave, esse campo não entra na definição das partições.
Obs: É possível definir “N” campos como partition key — a junção deles irá definir a partição.

● Columns: Representam as demais colunas da tabela, lista de tipos suportados na versão 3.3: Datastax types

Existem muitas configurações que podem ser definidas no momento da criação da column family. Seguem as principais:

● comments: Permite inserir comentários para documentação da tabela.
● default_time_to_live: Tempo de expiração do registro, após essa data o Cassandra marca o registro para ser excluído da tabela e ele não é mais retornado nas queries. Esse parâmetro pode ser sobrescrito no momento do insert.
● compression: É possível determinar qual algoritmo de compressão o Cassandra irá utilizar para essa determinada tabela, o default é o LZ4Compressor.
Para mais detalhes de configurações e descrição dos algoritmos de compressão: Datastax Tables

Row

As rows são compostas pela Primary key e um conjunto de columns. Vale ressaltar que o Cassandra persiste apenas os campos que contém valores, diferente dos bancos relacionais onde são alocados recursos para campos nulos. Isso significa que as rows podem conter colunas diferentes.

Column

A column é composta por basicamente 3 campos:
● Column key: Nome da coluna
● Column value: É o valor que está sendo persistido
● Timestamp: O Cassandra utiliza esse campo para resolver conflitos e determinar qual é o valor mais atual.

Agora que foram demonstradas as camadas de uma instância do Cassandra, iremos explorar as formas de montar um cluster.

O Cassandra é altamente escalável e sua arquitetura foi projetada para ser o mais resiliente possível. Segue abaixo o desenho de um cluster simples que roda em apenas um datacenter.

A replicação dos dados é feita com base no fator de replicação. Ela acontece no formato de anel, ou seja, o nó repassa os dados para a instância seguinte. Ainda utilizando apenas um datacenter, é possível configurar o cluster em racks diferentes, com isso o Cassandra irá replicar os dados entre eles para garantir a disponibilidade dos dados:

No exemplo acima está configurado o replication factor igual a 3, com isso o Cassandra irá colocar pelo menos uma cópia dos dados em cada rack. Para ambientes críticos pode-se criar essa estrutura multidacenter, para isso o keyspace obrigatoriamente deve estar configurado como NetworkTopologyStrategy.

Modelagem

A chave do sucesso para os projetos no Cassandra é a modelagem: É extremamente importante entender como o banco funciona antes de modelar o sistema. O Cassandra é extremamente eficiente para escrita e leitura desde que esteja bem modelado: os dados precisam estar desnormalizados! Provavelmente se terá uma tabela para cada consulta.

O Cassandra define a partição através do partition key (como explicado nos tópicos anteriores). Cada partição tem um coordinator — dessa forma, quando a aplicação precisa fazer uma query e o nó que é executado o comando não tem os dados, ele simplesmente redireciona a query para o coordinator dessa partição. O partition key deve ser informado na cláusula “Where em todas as queries”: caso isso não aconteça, o Cassandra não tem como identificar qual é o nó que possui o dado, e “espalha” a query pelo cluster inteiro. Conforme exemplo citado acima, 2 datacenters, com 2 racks (sendo que cada rack possui 5 máquinas): dado esse cenário, se não for indicado o partition key, o Cassandra irá executar a query em todas as máquinas (são 20 nodes). A ideia de ter muitas tabelas é justamente para mudar o partition key: dessa forma, sempre que for preciso executar uma query, o campo vai estar no partition key.

Como o Cassandra é extremamente performático para persistir os dados, não existe perda de performance com essa abordagem. A partir da versão 3.0 é possível criar uma view materializada trocando a partition key no momento da criação da view. Com isso a aplicação não tem a responsabilidade de persistir os dados em N tabelas.

Transações

No item acima diz-se que o ideal é a persistência dos dados em N tabelas, mas como realizar essa atividade já que o Cassandra não possui transações? E se está sendo gravado na tabela A e, ao gravar na tabela B, ocorre alguma exceção e o dados não são persistidos? A resposta é: utilizar a persistência atômica. Não se trata de uma transação, mas sim em executar um bloco de N comandos de uma só vez: com isso, os dados só serão persistidos se todos os comandos forem executados com sucesso. Um detalhe importante é que essa feature serve para persistir os dados de forma atômica: você não terá nenhum ganho de performance.

Consistência

Como o Cassandra leva alguns milissegundos para replicar os dados no cluster, suas queries podem não retornar a última versão dos registros, gerando um problema de consistência. Existem formas de contornar esse problema setando o nível de consistência (Consistency Level): é possível utilizar esse parâmetro tanto na escrita como na leitura (nesse link tem todas as possibilidades: Consistency level ).

Vantagens de utilizar o Cassandra

● Alta disponibilidade;
● Performance;
● Extremamente tolerante a falhas;
● Escalabilidade linear: se o banco atende 100K de requisições, para atender 200K basta dobrar a infraestrutura;
● Sem nenhum ponto único de falha;
● Altamente distribuído;
● Suporta N datacenters nativamente.

Quando não utilizar o Cassandra

Existem alguns cenários em que a utilização do Cassandra não é recomendável:

● Se precisar de muita consistência, a aplicação terá que garantir;
● Se o volume de dados ou o throughput da aplicação for muito pequeno;
● É necessário analisar se o modelo da aplicação suporta o paradigma colunar.

Mão na massa

Nesse tópico será feita uma instalação básica e será explorado a utilização do CQL-Cassandra Query Language. Essa é a linguagem utilizada para interagir com o banco, sua sintaxe é bem similar ao SQL. Pré requisitos: é necessário que o Java esteja instalado na máquina!

Instalando o banco de dados

O objetivo é fazer uma instalação bem simples para interação com o banco. Para isso, é necessário baixar o Cassandra através do link Download Cassandra.

Feito o download, descompacte o arquivo, os diretórios mais importantes são:
● bin: contém os scripts de interação com o Cassandra.
● data: contém os dados que o Cassandra armazena.
● conf: contém os yml’s de configuração do banco.

Nesse momento, consideraremos que o Java 1.8 já está instalado na máquina. Acesse o diretório /bin e execute o seguinte comando:

./cassandra

Caso esteja no Windows, utilize o arquivo .bat.

Pronto! O Cassandra já está rodando localmente em apenas um nó, por default ele sobe na porta: 9042.

Criando um keyspace

Neste momento vamos utilizar o cqlsh, é um sistema de command line que nos permite interagir com o Cassandra.

No diretório bin execute o seguinte comando:

./cqlsh

Você verá uma tela como essa:

Connected to Test Cluster at 127.0.0.1:9042.
[cqlsh 5.0.1 | Cassandra 3.11.2 | CQL spec 3.4.4 | Native protocol v4]
Use HELP for help.
cqlsh>

Nesse momento, ao estar conectado ao banco de dados, será criado o keyspace:

CREATE KEYSPACE blog
WITH
REPLICATION = {
'class': 'SimpleStrategy',
'replication_factor' : 1
};

Como está configurado apenas 1 nó no cluster, o fator de replicação será 1.

Criando tabelas no banco

Antes de criar a primeira tabela, é necessário setar o keyspace a ser utilizado:

cqlsh> use blog;

No console irá aparecer o keyspace utilizado:

cqlsh:blog>

O script abaixo cria a tabela no banco de dados.

CREATE TABLE posts (
tag varchar,
name varchar,
author varchar,
description text,
likes int,
PRIMARY KEY (tag, name)
);

O campo tag é a partition key e a row key é composta por tag + name.

Inserindo registros

Execute o comando abaixo:

insert into blog.posts (tag, name, author, description, likes) 
values
('apache-cassandra','Cassandra post','Jose','post do cassandra',0);

A seguir, execute uma query simples, apenas para ratificar que o registro foi inserido:

cqlsh:blog> select * from posts;

Resultado:

tag             |name           |author |description       |likes
apache-cassandra|Cassandra post |Jose |post do cassandra |0
(1 rows)
cqlsh:blog>

O Cassandra é idempotente, ou seja, se um determinado insert for executado 2 vezes, o registro será alterado no banco. Para testar esse cenário, será executado o insert modificando a quantidade de likes:

insert into blog.posts (tag, name, author, description, likes)
values
('apache-cassandra','Cassandra post','Jose','post do cassandra',1);

Executando o mesmo select:

tag             |name           |author |description       |likes
apache-cassandra|Cassandra post |Jose |post do cassandra |1
(1 rows)
cqlsh:blog>

A cláusula update pode ser utilizada para alterar os registros, já a row key é obrigatória:

update posts set likes = 2 
where tag = 'apache-cassandra' and name = 'Cassandra post';

Verificando o resultado:

select * from posts;

tag |name |author |description | likes
apache-cassandra|Cassandra post |Jose |post do cassandra | 2

(1 rows)

A parte idempotente do Cassandra também funciona para updates, como por exemplo, ao tentar realizar uma atualização do registro cujo a chave ainda não exista:

update posts set likes = 2 
where tag = 'apache-kafka' and name = 'Kafka post';

Verificando o resultado:

cqlsh:blog> select * from posts;

tag |name |author |description |likes
apache-cassandra |Cassandra post |Jose |post do cassandra |2
apache-kafka |Kafka post |null |null |2

(2 rows)

Note que um novo registro foi inserido e os campos que não foram informados ficaram nulos.

Fazendo queries

As condições nas queries são permitidas apenas se a partition key for informada:

cqlsh:blog> select * from posts where tag =   'apache-cassandra';

tag |name |author |description |likes
apache-cassandra |Cassandra post |Jose |post do cassandra |2

(1 rows)

Caso não seja informada a partition key, o Cassandra irá apresentar um erro:

cqlsh:blog> select * from posts where likes = 1;InvalidRequest: Error from server: code=2200 [Invalid query] message="Cannot execute this query as it might involve data filtering and thus may have unpredictable performance. If you want to execute this query despite the performance unpredictability, use ALLOW FILTERING"

É possível fazer essa query utilizando a cláusula “ALLOW FILTERING”, entretanto, não é recomendado utilizar essa abordagem pois poderá ocasionar sérios problemas de performance:

cqlsh:blog> select * from posts where likes = 2 allow filtering;

tag |name |author |description |likes
apache-cassandra |Cassandra post |Jose |post do cassandra |2
apache-kafka |Kafka post |null |null |2

(2 rows)

Criando views materializadas

A partir da versão 3.0 do Cassandra, é possível utilizar as views materializadas. Segue exemplo de criação:

create materialized view posts_by_likes as 
select tag, name, author, description, likes
from posts
where tag is not null
and name is not null
and likes is not null
PRIMARY KEY (likes, tag, name);

A partition key da view é o campo likes. Com isso, pode-se fazer a query filtrando pela quantidade de likes sem utilizar a cláusula “ALLOW FILTERING”:

cqlsh:blog> select * from posts_by_likes where likes = 2;

likes |tag |name |author |description
2 |apache-cassandra |Cassandra post |Jose |post do cassandra
2 |apache-kafka |Kafka post |null |null

(2 rows)

Dicas importantes para o ambiente de produção

Listamos abaixo as principais lições aprendidas com o Cassandra no ambiente de produção:

● Não utilize o consistency level ALL: o Cassandra é tolerante a falhas, porém se utilizar essa configuração, sua aplicação irá perder performance e não irá mais suportar falhas. Se um nó do Cassandra falhar, sua aplicação irá falhar junto;

● Cuidado com as views materializadas: é necessário testar bem antes de utilizar em produção;

● Nunca utilize a cláusula “ALLOW FILTERING”: esta cláusula permite filtrar por campos que não estão na partition key, e com isso o Cassandra vai executar a query no cluster inteiro causando sérios problemas de performance;

● É possível utilizar a cláusula “in” e passar o partition key, porém, dependendo da quantidade de registros no “in”, pode-se prejudicar o cluster. É melhor fazer “N” queries do que utilizar essa cláusula;

● Monitore! O Cassandra expõe suas métricas através de jmx, e é possível capturá-las e visualizar através do Grafana.
Lista de variáveis: Métricas
Um exemplo de framework que captura e envia para o Graphite (banco de dados do Grafana): metrics-graphite;

● Caso utilize um TTL pequeno, é necessário alterar o algoritmo de compactação para TimeWindowCompactionStrategy e configurar o intervalo de compactação manualmente, caso contrário o Cassandra irá demorar (default 10 dias) para desalocar o espaço em disco;

● É importante seguir as recomendações de hardware para o ambiente de produção (Planning Hardware).

Conclusão

Devido ao aumento dos dados nas aplicações atuais, a utilização do Cassandra vem aumentando muito no mercado, e pode-se concluir que é um banco extremamente performático e resiliente. Caso se encaixe no seu cenário (dadas as ressalvas citadas no artigo), recomendamos fortemente seu uso!

Entre para nosso time

--

--