02 - NoSQL (MongoDB, DynamoDB, Cassandra)
video: https://youtu.be/... no frontmatter da nota.NoSQL (MongoDB, DynamoDB, Cassandra)
NoSQL é a designação para bancos de dados não relacionais que sacrificam partes do modelo clássico, como esquema rígido e joins, em troca de escala horizontal, flexibilidade de dados e disponibilidade. A categoria surgiu como resposta a cargas que o relacional atendia com custo crescente, como catálogos com estruturas variáveis, sessões de usuários e telemetria em volume massivo. Em produção, NoSQL não substitui o relacional, e sim complementa quando o padrão de acesso está claro e o volume de escala exige particionamento natural. Os três exemplos mais representativos representam famílias distintas: MongoDB é orientado a documentos, DynamoDB é chave-valor gerenciado com ampla adoção em nuvem, e Cassandra é wide-column voltado a escrita intensa multi-datacenter. A decisão entre eles começa pela pergunta certa: quais perguntas o sistema fará aos dados, e com que frequência.
Conceito
A família NoSQL se organiza em quatro grandes famílias, das quais três concentram a maioria dos casos reais. Bancos de documentos (MongoDB) agrupam dados em documentos semiestruturados, tipicamente JSON ou BSON, consultáveis por índices secundários. Bancos chave-valor (DynamoDB, na essência) associam uma chave única a um valor opaco, oferecendo leitura e escrita previsíveis por chave. Bancos wide-column (Cassandra) armazenam linhas com conjuntos de colunas flexíveis, particionadas por chave e otimizadas para varredura de partições. Existe ainda a família de grafos, relevante para relacionamentos densos, mas fora do escopo deste arquivo. O traço comum é a modelagem guiada pelo padrão de acesso: você desenha a tabela ou coleção sabendo previamente como os dados serão lidos, invertendo a lógica do relacional, onde o modelo descreve as entidades e as consultas emergem depois.
Que problema resolve
O NoSQL ataca três limites históricos do modelo relacional em escala. O primeiro é o custo de escala vertical: quando a carga supera o que um único servidor atende, o particionamento horizontal nativo distribui dados por muitos nós comuns, em vez de migrar para hardware cada vez maior. O segundo é a rigidez de esquema: documentos sem estrutura fixa absorvem atributos variáveis por registro, úteis em catálogos e integrações heterogêneas, sem migração a cada mudança. O terceiro é a disponibilidade em rede particionada: muitos motores NoSQL adotam consistência eventual ajustável, aceitando respostas levemente defasadas para seguir disponíveis durante partições, conforme o teorema CAP. Há também o ganho de previsibilidade de latência: motores como DynamoDB entregam leituras de milissegundo único com custo determinístico, o que simplifica dimensionamento de serviços de altíssimo tráfego. Nenhuma dessas vantagens é gratuita; cada uma cobra em consistência, consultabilidade ou complexidade operacional, e o desenho precisa pagar o preço certo para o caso certo.
Funcionamento interno
Apesar das diferenças, os três motores compartilham mecanismos internos que explicam seu comportamento em produção. Entendê-los evita decisões guiadas apenas por marketing.
- Particionamento: a chave de partição define onde cada dado vive. No MongoDB é a shard key, no DynamoDB é a partition key, no Cassandra é a partition key do modelo de dados; escolha ruim gera partições quentes e desbalanceamento.
- Replicação: cada partição é replicada em vários nós. Cassandra usa topologia de anel com fator de replicação por datacenter; MongoDB usa replica sets com eleição de primário; DynamoDB replica transparentemente entre zonas de disponibilidade.
- Modelo de consistência ajustável: Cassandra permite escolher por consulta o quorum de leitura e escrita; DynamoDB oferece consistência eventual ou forte por chamada; MongoDB suporta read e write concerns configuráveis.
- Escrita otimizada por log: Cassandra e DynamoDB gravam em commit log e memtable, descarregando depois em SSTables imutáveis; a escrita é sempre sequencial, o que sustenta throughput alto e constante.
- Tunable storage: no Cassandra, compaction mescla SSTables em segundo plano, e a configuração de compactação afeta diretamente latência de leitura e uso de disco.
// MongoDB: modelagem de catálogo com documento incorporado
// Acessos mais comuns: buscar produto por id e listar variações
db.produtos.insertOne({
_id: "sku-4821",
nome: "Fone Bluetooth Pro",
marca: "AudioX",
preco: { valor: 349.90, moeda: "BRL" },
variacoes: [
{ cor: "preto", estoque: 120 },
{ cor: "branco", estoque: 87 }
],
atributos: {ANC: true, carga_rapida: "30min", peso_g: 245}
});
// Índice secundário para busca por marca
db.produtos.createIndex({ marca: 1, "preco.valor": 1 });
# DynamoDB (boto3): acesso previsível por chave
# Consulta única: buscar pedidos do cliente, ordenados por data
tabela.put_item(Item={
"pk": "CLIENTE#123", # partition key: agrupa por cliente
"sk": "PEDIDO#2025-06-14T10:30", # sort key: ordena no tempo
"status": "pago",
"total_centavos": 15980,
})
resp = tabela.query(
KeyConditionExpression=Key("pk").eq("CLIENTE#123") &
Key("sk").begins_with("PEDIDO#"),
ScanIndexForward=False, # mais recente primeiro
Limit=10,
)
-- Cassandra: tabela modelada pelo padrão de leitura
-- Pergunta: listar eventos do dispositivo em janela de tempo
CREATE TABLE telemetria.eventos_dispositivo (
dispositivo_id uuid,
dia date,
ts timestamp,
metrica text,
valor double,
PRIMARY KEY ((dispositivo_id, dia), ts)
) WITH CLUSTERING ORDER BY (ts DESC) AND default_time_to_live = 90;
-- Consulta eficiente: partição inteira no nó correto, sem fan-out
SELECT ts, metrica, valor FROM telemetria.eventos_dispositivo
WHERE dispositivo_id = 5f3e... AND dia = '2025-06-14' AND ts >= '2025-06-14 00:00';
Modelagem pelo padrão de acesso
A regra de ouro do NoSQL é modelar a partir das consultas, e não das entidades. Cada padrão de acesso frequente merece uma estrutura dedicada, mesmo que isso signifique duplicar dados deliberadamente. No DynamoDB, isso se materializa em single-table design, em que múltiplas entidades convivem na mesma tabela com prefixos de chave (CLIENTE#, PEDIDO#) e índices secundários globais para acessos alternativos. No Cassandra, cada query pattern gera uma tabela, e joins não existem; duplicação e desnormalização são esperadas. No MongoDB, o debate central é incorporar versus referenciar: dados que vivem e morrem juntos (variações de um produto) ficam incorporados; dados que crescem sem limite (histórico de pedidos) ficam em coleção própria. O custo dessa abordagem é a dificuldade de responder perguntas não previstas no desenho, o que exige camada analítica separada quando a exploração de dados é frequente.
Consistência ajustável e trade-offs
A consistência eventual não é uma propriedade binária, e os motores oferecem controles finos que devem ser tratados como decisão arquitetural. Em Cassandra, LOCAL_QUORUM para leitura e escrita garante interseção de réplicas dentro do datacenter local, enquanto ONE prioriza latência e tolera dados defasados. Em DynamoDB, leituras fortes custam o dobro das eventuais, e o padrão deve ser eventual sempre que a UX tolerar segundos de defasagem. O padrão read-your-own-writes, essencial para experiência de usuário, resolve-se com leituras fortes por período curto após a escrita ou com sessões sticky. Trabalhar com consistência eventual exige versões e timestamps nos dados, lógica de resolução de conflito em contadores e carrinhos, e clareza sobre quais invariáveis são absolutas; dinheiro, normalmente, permanece em sistema relacional ou com transações fortes.
Comparação entre os três motores
| Critério | MongoDB | DynamoDB | Cassandra |
|---|---|---|---|
| Família | Documentos | Chave-valor gerenciado | Wide-column |
| Modelo de custo | Instâncias (self-hosted ou Atlas) | Por requisição (RCU/WCU) ou capacidade provisionada | Nós de cluster (self-hosted ou Keyspaces) |
| Escala | Sharding manual de shard key | Automático por partition key | Automático por anel consistente |
| Joins | $lookup limitado |
Não suportado | Não suportado |
| Transações | Multi-documento ACID (4.0+) | Transações por item e entre itens com custo extra | LWT para CAS por partição |
| Consultas ad hoc | Ricas com índices secundários | Restritas a chave e índices globais | Restritas à chave de partição |
| Ponto forte | Flexibilidade com consultabilidade | Latência previsível e operação zero | Escrita massiva multi-datacenter |
MongoDB se encaixa em catálogos, perfis e agregações de conteúdo, onde a estrutura varia por registro, mas as consultas precisam de flexibilidade. DynamoDB é a escolha natural em arquiteturas serverless e serviços com acesso por chave conhecida, eliminando gestão de infraestrutura. Cassandra domina ingestão contínua, telemetria e histórico de atividades, especialmente com exigência de sobrevivência à perda de um datacenter. Combinações são comuns: pedido transacional no relacional, catálogo no MongoDB, rastro de eventos no Cassandra.
Erros comuns
- Shard key ou partition key de baixa cardinalidade, que concentra tudo em poucas partições quentes e destrói o benefício da escala horizontal.
- Modelar coleções como tabelas relacionais e descobrir em produção que as consultas exigem varreduras completas, sem índice útil.
- Confiar na consistência eventual em fluxos que exigem read-your-own-writes, gerando reclamações de dados que não aparecem após a gravação.
- Crescimento ilimitado de documentos incorporados, estourando limites de tamanho (16 MB no MongoDB) e degradando reescritas de documento.
- Varreduras (
Scan) no DynamoDB em rotina de aplicação, custo alto e latência imprevisível; varredura é ferramenta de análise, não de serviço. - Ignorar time-to-live e retenção, acumulando dados frios que encarecem armazenamento e pioram compaction e backups.
- Planejar joins mentais no lado da aplicação com N+1 chamadas, anulando o ganho de latência que motivou a escolha do NoSQL.
- Subestimar o custo de índices secundários globais, que dobram a escrita de cada item e consomem capacidade própria no DynamoDB.
- Trocar de banco por modismo sem revisar o padrão de acesso, migrando o problema de modelagem em vez de resolvê-lo.
- Deixar o tamanho de item ou de linha crescer sem controle, aumentando custo por requisição (DynamoDB) e pressão de heap (Cassandra, MongoDB).
- Operar Cassandra sem gestão de tombstones, acumulando deleções que inflam compaction e leituras por semanas.
Boas práticas
- Liste os padrões de acesso antes de desenhar o modelo; cada consulta frequente deve mapear para uma estrutura e um índice específicos.
- Defina a chave de partição pensando em distribuição e em acessos conjuntos, testando com dados de produção mascarados antes do go-live.
- Fixe por serviço a política de consistência: leitura eventual como padrão, forte apenas onde a invariante exige, com justificativa documentada.
- Estabeleça TTL e ciclo de vida de dados no desenho, integrando arquivamento para armazenamento barato quando o histórico precisa ser mantido.
- Use transações nativas com parcimônia: elas existem, mas custam mais e reduzir a parcela transacional é o que mantém a escala previsível.
- Monitore partições quentes, taxa de throttling (DynamoDB) e latência por percentil, pois degradação silenciosa é o modo de falha típico.
- Mantenha a camada analítica separada: exporte para data warehouse ou lakehouse as perguntas ad hoc, preservando o NoSQL para os acessos previstos.
- Trate mudanças de modelo como migrações versionadas e testadas, com carga real em staging para validar distribuição de partições.
- Defina limites de alerta por throttle, quorum failures e latência p99, com runbook para cada um desses sintomas.
- Documente em ADR a escolha do motor e a chave de partição adotada, pois ambas são decisões difíceis de reverter depois do volume crescer.