Introdução ao Apache Cassandra: quando um banco wide-column faz sentido
1. Fundamentos do Modelo Wide-Column no Cassandra
O Apache Cassandra é um banco de dados NoSQL do tipo wide-column, projetado para lidar com grandes volumes de dados distribuídos em múltiplos nós. Diferente dos bancos relacionais tradicionais, o Cassandra organiza dados em famílias de colunas (column families), onde cada linha pode ter um conjunto diferente de colunas — um conceito conhecido como tabelas esparsas.
No modelo relacional (SQL), você define um esquema fixo com colunas predefinidas. No MongoDB (documentos), você armazena JSON flexível. O Cassandra combina o melhor dos dois: oferece estrutura com tabelas, mas permite que cada linha tenha colunas dinâmicas. Isso é particularmente útil quando você não sabe antecipadamente todos os atributos que um registro terá.
A estrutura hierárquica do Cassandra é:
- Keyspace: equivalente a um "banco de dados" no SQL
- Table: família de colunas
- Partition: unidade de distribuição de dados no cluster
- Row: conjunto de colunas dentro de uma partição
- Clustering columns: definem a ordem de armazenamento dentro de uma partição
Keyspace: sistema_logs
Table: logs_iot
Partition Key: data_hora (dia/hora)
Clustering Key: timestamp
Row: { sensor_id: "A1", temperatura: 25.3, umidade: 60 }
2. Arquitetura Distribuída e Sem Mestre (Masterless)
O Cassandra opera em um modelo masterless (sem mestre), onde todos os nós são iguais. O cluster forma um anel (ring) lógico, e cada nó é responsável por uma faixa de dados determinada pelo particionador (partitioner) — geralmente o Murmur3Partitioner.
A replicação é controlada pelo replication factor (RF). Se RF=3, cada dado é armazenado em três nós diferentes. A consistência é ajustável por consulta:
- ONE: resposta do primeiro nó disponível (baixa latência, menor consistência)
- QUORUM: maioria dos nós réplicas (equilíbrio entre latência e consistência)
- ALL: todos os nós réplicas (alta consistência, maior latência)
O Gossip protocol permite que cada nó descubra o estado dos outros nós a cada segundo, sem necessidade de um coordenador central. Se um nó falha, o cluster continua operando normalmente.
Exemplo de configuração de consistência:
INSERT INTO logs (id, mensagem) VALUES (1, 'erro')
USING CONSISTENCY QUORUM;
3. Modelagem de Dados para Cassandra: Pensando em Consultas Primeiro
A modelagem no Cassandra segue o princípio query-first: você primeiro define quais consultas serão feitas e depois cria as tabelas para atendê-las. Isso é oposto ao SQL, onde você normaliza os dados primeiro.
A chave primária composta é essencial:
- Partition key: determina onde os dados são armazenados (distribuição)
- Clustering columns: ordenam os dados dentro da partição
Exemplo: Sensores IoT (séries temporais)
CREATE TABLE sensores.temperatura (
sensor_id text,
data_hora timestamp,
temperatura float,
PRIMARY KEY ((sensor_id), data_hora)
) WITH CLUSTERING ORDER BY (data_hora DESC);
Aqui, sensor_id é a partition key, e data_hora é a clustering column ordenada decrescentemente. Isso permite consultas eficientes como "últimas 100 leituras do sensor A1".
Exemplo: Catálogo de Produtos
CREATE TABLE catalogo.produtos_por_categoria (
categoria text,
produto_id uuid,
nome text,
preco decimal,
PRIMARY KEY ((categoria), preco, produto_id)
) WITH CLUSTERING ORDER BY (preco ASC);
4. Quando o Cassandra é a Escolha Certa (e Quando Não)
Casos de uso ideais:
- Escrita intensa: milhares de escritas por segundo (logs, métricas IoT)
- Escalabilidade horizontal: adicionar nós sem downtime
- Alta disponibilidade: sem ponto único de falha, failover automático
- Dados geo-distribuídos: replicação entre data centers
Cenários inadequados:
- Joins complexos: o Cassandra não suporta JOINs nativos
- Transações ACID: apenas consistência eventual ou ajustável
- Consultas ad-hoc: você precisa modelar para consultas específicas
- Dados pequenos e relacionais: overhead desnecessário
Comparado com PostgreSQL e CockroachDB:
- PostgreSQL: consistência forte, ideal para dados relacionais, mas escala verticalmente
- CockroachDB: consistência forte distribuída, mas mais complexo e maior latência
- Cassandra: consistência eventual, menor latência em escritas, melhor para cargas massivas
5. Operações Essenciais: Inserção, Leitura e Compaction
CQL (Cassandra Query Language)
-- Criação
CREATE KEYSPACE exemplo WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 3};
USE exemplo;
CREATE TABLE usuarios (
id uuid PRIMARY KEY,
nome text,
email text
);
-- Inserção
INSERT INTO usuarios (id, nome, email)
VALUES (uuid(), 'João Silva', 'joao@email.com');
-- Leitura
SELECT * FROM usuarios WHERE id = ?;
-- Atualização
UPDATE usuarios SET email = 'novo@email.com' WHERE id = ?;
-- Exclusão
DELETE FROM usuarios WHERE id = ?;
Como o Cassandra lê:
- Verifica o memtable (cache em RAM)
- Verifica SSTables (arquivos em disco) usando bloom filters para evitar leituras desnecessárias
- Índices secundários podem ser usados, mas são menos eficientes que consultas por partition key
Compaction:
- SizeTieredCompactionStrategy (STCS): mescla SSTables de tamanho similar (bom para escritas pesadas)
- LeveledCompactionStrategy (LCS): mantém SSTables em níveis (melhor para leituras)
ALTER TABLE logs WITH compaction = {'class': 'LeveledCompactionStrategy'};
6. Estratégias de Particionamento e Hot/Cold Data
A escolha da partition key é crítica para evitar hot spots — partições que recebem carga desproporcional. Uma partition key mal escolhida pode sobrecarregar um nó enquanto outros ficam ociosos.
Exemplo de hot spot (ruim):
PRIMARY KEY ((data_hora)) -- Uma partição por hora, carga concentrada
Distribuição uniforme (bom):
PRIMARY KEY ((sensor_id, data_hora)) -- Combinação única distribui carga
TTL (time-to-live) permite expirar dados automaticamente:
INSERT INTO logs (id, mensagem) VALUES (1, 'erro') USING TTL 86400; -- 24 horas
Para dados históricos, use compressão:
CREATE TABLE logs_historicos WITH compression = {'class': 'LZ4Compressor'};
7. Monitoramento e Manutenção do Cluster
Métricas essenciais:
- Latência de leitura/escrita: p95, p99
- Pending compactions: número de compactações pendentes
- Hinted handoffs: entregas atrasadas de dados para nós temporariamente offline
- Bloom filter hit rate: eficiência na filtragem de SSTables
Ferramentas:
nodetool status # Ver estado do cluster
nodetool repair # Reparar consistência entre réplicas
nodetool cfstats # Estatísticas de tabelas
nodetool compactionstats # Status de compactações
Para monitoramento contínuo, integre com Prometheus e visualize no Grafana.
nodetool repair é crucial para garantir consistência quando nós ficam offline temporariamente. Execute regularmente em clusters de produção.
8. Exemplo Prático: Construindo um Sistema de Logs Distribuído
Modelagem da tabela
CREATE KEYSPACE logs_distribuidos
WITH replication = {'class': 'NetworkTopologyStrategy', 'datacenter1': 3};
USE logs_distribuidos;
CREATE TABLE logs_por_dia (
dia text, -- Partition key: "2025-03-15"
timestamp timeuuid, -- Clustering column (ordenado)
servico text,
severidade text, -- "INFO", "WARN", "ERROR"
mensagem text,
PRIMARY KEY ((dia), timestamp, servico)
) WITH CLUSTERING ORDER BY (timestamp DESC);
Inserção em lote vs. assíncrona
Para alta throughput, use inserção assíncrona com prepared statements:
-- Inserção assíncrona (recomendada para alta throughput)
INSERT INTO logs_por_dia (dia, timestamp, servico, severidade, mensagem)
VALUES ('2025-03-15', now(), 'api-gateway', 'ERROR', 'Timeout na conexão');
-- Batch (apenas para operações atômicas relacionadas)
BEGIN BATCH
INSERT INTO logs_por_dia (dia, timestamp, servico, severidade, mensagem)
VALUES ('2025-03-15', now(), 'auth', 'INFO', 'Login bem-sucedido');
INSERT INTO logs_por_dia (dia, timestamp, servico, severidade, mensagem)
VALUES ('2025-03-15', now(), 'auth', 'INFO', 'Token gerado');
APPLY BATCH;
Consulta por intervalo de tempo e filtro por severidade
-- Últimas 100 entradas do dia
SELECT * FROM logs_por_dia
WHERE dia = '2025-03-15'
ORDER BY timestamp DESC
LIMIT 100;
-- Filtro por severidade (usando índice secundário)
CREATE INDEX idx_severidade ON logs_por_dia (severidade);
SELECT * FROM logs_por_dia
WHERE dia = '2025-03-15'
AND severidade = 'ERROR';
Para consultas frequentes por severidade, crie uma tabela separada:
CREATE TABLE logs_por_severidade (
severidade text,
dia text,
timestamp timeuuid,
servico text,
mensagem text,
PRIMARY KEY ((severidade, dia), timestamp)
) WITH CLUSTERING ORDER BY (timestamp DESC);
Referências
- Documentação Oficial do Apache Cassandra — Guia completo de instalação, configuração e operação do Cassandra
- Cassandra Data Modeling Guide - DataStax — Tutorial oficial sobre modelagem orientada a queries
- Cassandra Query Language (CQL) Reference — Referência completa da sintaxe CQL
- Understanding Compaction in Cassandra - Instaclustr — Artigo técnico sobre estratégias de compactação e desempenho
- Cassandra vs MongoDB vs PostgreSQL: When to Use Which — Comparação prática entre bancos NoSQL e relacionais
- Monitorando Cassandra com Prometheus e Grafana — Guia oficial de monitoramento de clusters Cassandra
- Cassandra Partitioning Strategies and Hot Spot Prevention — Artigo sobre escolha de partition keys e prevenção de hot spots