NoSQL¶
Bancos NoSQL ("não apenas SQL") são uma família de bancos mais flexíveis que o modelo relacional, pensada para grandes volumes, formatos variados e escala horizontal. Não substituem o relacional: cada modelo resolve melhor um tipo de problema. Conceitos de SQL e modelagem relacional ficam em SQL e Modelagem de Dados.
SQL x NoSQL¶
| SQL (relacional) | NoSQL | |
|---|---|---|
| Estrutura | Tabelas com esquema definido e relações entre registros | Documentos, chave-valor, grafos ou colunas |
| Esquema | Definido antes de salvar | Pode variar conforme a necessidade |
| Consistência | Prioriza transações e regras estruturadas | Muitos priorizam escala e flexibilidade |
| Quando usar | Finanças, pedidos, cadastros, sistemas com muitos relacionamentos | Grandes volumes, formatos variados, alta escala de leitura ou escrita |
Os principais modelos¶
Banco de documentos¶
Armazena registros como documentos, em geral parecidos com JSON; cada documento pode conter campos, listas e objetos aninhados, e dois documentos da mesma coleção podem ter campos diferentes. Exemplo: um produto guardando nome, preço, imagens, categorias e avaliações em um único documento. Combina bem com APIs e aplicações que trabalham com JSON. Cuidado: evite documentos gigantes e pense em como os dados serão consultados e atualizados. (Exemplo clássico: MongoDB.)
Chave-valor¶
Cada dado é guardado como uma chave única ligada a um valor (usuario:42 →
preferências ou dados de sessão). A busca por chave é simples e muito rápida, com baixa
latência e fácil escalabilidade. Usos típicos: cache, sessões, carrinho de compras,
contadores e dados temporários. Limite: não é a melhor escolha para consultas complexas
com muitos filtros e relacionamentos. (Exemplo: Redis.)
Banco de colunas (famílias de colunas)¶
Agrupa dados em famílias de colunas, não em tabelas rígidas. Lida bem com grande volume de escrita distribuída em vários servidores; exige modelar as colunas pensando nas consultas que a aplicação realmente fará. Exemplo: eventos por usuário e data, logs de acesso, telemetria. Cuidado: relacionamentos e consultas ad hoc são mais limitados que em bancos relacionais. (Exemplo: Cassandra.)
Banco de grafos¶
Representa nós (entidades) e arestas (relações entre elas): pessoa → segue →
pessoa; cliente → comprou → produto. Encontra caminhos, conexões, recomendações e graus de
relacionamento sem encadear muitos JOINs. Aplicações: redes sociais, detecção de fraude,
rotas, recomendações e gestão de dependências. Cuidado: escolha quando as conexões forem
centrais; não substitui todo tipo de banco. (Exemplo: Neo4j.)
Séries temporais¶
Registros acompanhados por data e hora: medições, eventos, leituras de sensores (preço de ações, uso de CPU, vendas por hora). A ordem do tempo é essencial; agregados por minuto, hora, dia ou mês aceleram análises. Usos: monitoramento, IoT, métricas de aplicações, finanças, previsões. Cuidado: defina retenção, compressão e índices de tempo para grandes volumes.
Busca textual¶
Localiza documentos pelo conteúdo de textos longos, usando um índice de texto que organiza termos para busca mais rápida do que ler cada texto inteiro. Oferece palavras-chave, frases, filtros, sinônimos e correção de termos, com resultados ordenados por relevância. Exemplo: buscar produtos por descrição ou artigos por assunto. Cuidado: atualize os índices e trate idioma, acentos e palavras muito comuns (stop words, ver Machine Learning).
Dados em sistemas distribuídos¶
Teorema CAP¶
Em sistemas distribuídos, servidores podem ficar separados por falhas de rede. O teorema CAP diz que não dá para garantir ao mesmo tempo as três propriedades abaixo durante uma partição:
Definição: Teorema CAP
- C — Consistência (Consistency): toda leitura recebe a versão mais recente do dado ou uma mensagem de erro.
- A — Disponibilidade (Availability): toda requisição recebe uma resposta, mesmo durante falhas.
- P — Tolerância a partição (Partition tolerance): o sistema continua funcionando quando há perda de comunicação entre nós.
Como partições de rede acontecem, na prática a escolha é entre priorizar consistência ou disponibilidade quando ela ocorre. Entenda a necessidade do produto antes de definir arquitetura e banco.
Consistência eventual¶
Definição: Consistência eventual
Após uma atualização, algumas réplicas podem demorar um pouco para mostrar o novo dado; sem novas mudanças, elas convergem para o mesmo valor. Ajuda a manter disponibilidade e escala em sistemas distribuídos.
- Quando usar: métricas, feeds, catálogos e dados que toleram uma pequena demora (ex.: uma curtida ou contador pode aparecer diferente por alguns segundos em regiões distintas).
- Cuidado: não use em operações críticas que exigem leitura imediata e exata, como saldo bancário.
Comparar com as garantias ACID das transações relacionais em SQL — transações.
MongoDB em profundidade¶
O MongoDB é o banco de documentos mais usado. Esta seção mostra como ele funciona na prática, sempre comparando com o SQL
que você já conhece. (Os exemplos usam o mongosh, o shell atual.)
Conceitos¶
Definição: documento, collection e database no MongoDB
Documento é um registro em formato JSON que reúne todas as informações de uma entidade — pode ter valores simples,
listas e outros documentos aninhados. Collection é um conjunto de documentos (equivale a uma tabela, mas sem esquema
fixo). Database agrupa collections. Cada documento tem um campo _id único, indexado automaticamente.
Definição: JSON e BSON
JSON é um formato textual simples (objetos {}, listas [], textos, números, booleanos, null) criado para trafegar dados entre
servidor e navegador. Ele não define data nem dado binário, por isso o MongoDB usa o BSON (Binary JSON), uma extensão binária
com mais tipos (Date, ObjectId, inteiros de 32/64 bits, decimal, binário).
Dois conceitos de escala: replica set (cópias do mesmo banco em vários servidores, para alta disponibilidade) e sharding (particionar uma collection entre servidores quando ela passa do que cabe em um só).
CRUD e operadores, comparado ao SQL¶
| Operação | SQL | MongoDB (mongosh) |
|---|---|---|
| Criar | INSERT INTO pedidos ... |
db.pedidos.insertOne({...}), insertMany([...]) |
| Consultar | SELECT * FROM pedidos WHERE total > 100 |
db.pedidos.find({ total: { $gt: 100 } }) |
| Projeção | SELECT cliente, total |
find({}, { cliente: 1, total: 1, _id: 0 }) |
| Ordenar e limitar | ORDER BY total DESC LIMIT 5 |
find().sort({ total: -1 }).limit(5) |
| Atualizar | UPDATE ... SET status = 'pago' |
updateOne({ _id: 1 }, { $set: { status: "pago" } }) |
| Incrementar | SET saldo = saldo + 10 |
updateOne(filtro, { $inc: { saldo: 10 } }) |
| Remover | DELETE FROM pedidos WHERE ... |
deleteOne(filtro), deleteMany(filtro) |
| Valores distintos | SELECT DISTINCT status |
db.pedidos.distinct("status") |
| Contar | SELECT COUNT(*) |
countDocuments(filtro) |
| Remover tabela | DROP TABLE |
db.pedidos.drop() |
Operadores começam com $:
| Tipo | Operadores | Equivalente em SQL |
|---|---|---|
| Comparação | $gt, $gte, $lt, $lte, $ne |
>, >=, <, <=, <> |
| Conjunto | $in, $nin |
IN, NOT IN |
| Lógicos | $and, $or, $nor, $not |
AND, OR, NOT |
| Existência | $exists: true/false |
IS NOT NULL (útil porque o esquema é flexível) |
| Texto | { nome: /silva$/i } ou $regex |
LIKE '%silva' (o i ignora maiúsculas; ^ e $ ancoram início e fim) |
Validação e coleções especiais. Embora o esquema seja flexível, é possível exigir estrutura com um validator ($jsonSchema:
tipos, campos obrigatórios, padrões). Uma capped collection tem tamanho fixo e descarta os documentos mais antigos (um buffer
circular, bom para logs recentes).
Modelagem (schema design): a aplicação manda¶
No modelo relacional você normaliza pensando nos dados; no MongoDB você modela pensando em como a aplicação lê. Se uma tela exibe um prêmio com seus autores, tipo e ano, o ideal é um documento com tudo (em vez de juntar cinco tabelas). É a mesma ideia da desnormalização do data warehouse, aplicada ao banco operacional. "De quantas tabelas preciso para exibir isto? Cinco. De quantas collections? Uma."
| Relacionamento | Opção 1: embutir (embedded) | Opção 2: referenciar |
|---|---|---|
| Um para muitos (livro → comentários) | Array de comentários dentro do livro: uma leitura, melhor desempenho | Duas collections e uma busca por livro_id: lembra o relacional, sem vantagem no MongoDB, exceto se os filhos forem enormes ou muito acessados à parte |
| Muitos para muitos (livros ↔ categorias) | Cada lado guarda a lista de _id do outro (sem tabela intermediária) |
|
| Tudo numa collection | Mais intuitivo e rápido | Documentos grandes (limite de 16 MB), dados repetidos para atualizar, índices em subdocumentos mais complexos |
Exemplo: um filme com categorias e diretores como listas simples e atores como documentos aninhados.
db.filmes.insertOne({
titulo: "Exemplo", ano: 1999, nota: 8.1, votos: 52000,
categorias: ["Drama", "Crime"],
diretores: ["Pessoa A"],
atores: [ { nome: "Pessoa B", sexo: "F" }, { nome: "Pessoa C", sexo: "M" } ]
})
Critério prático: embuta o que é lido junto e tem ciclo de vida junto (e não cresce sem limite); referencie o que é compartilhado, muito grande, ou acessado de forma independente.
Migrando de um banco relacional: uma rotina lê as tabelas, monta o documento (juntando listas de tabelas auxiliares) e o insere na
collection. Dicas para cargas grandes: insertMany em lotes em vez de um documento por vez, e relaxar o write concern (por padrão
o cliente espera a confirmação de cada gravação; w: 0/unacknowledged não espera, e é mais rápido, mas sem garantia). Para a nuvem existe o MongoDB
Atlas, com camada gratuita.
Aggregation framework¶
Consultas de agregação (o GROUP BY do SQL) usam um pipeline: uma lista de estágios em que a saída de um alimenta o próximo.
db.pedidos.aggregate([
{ $match: { status: "pago" } }, // WHERE
{ $group: { _id: "$cliente", total: { $sum: "$valor" }, // GROUP BY cliente
pedidos: { $sum: 1 }, media: { $avg: "$valor" } } },
{ $match: { total: { $gt: 1000 } } }, // HAVING
{ $sort: { total: -1 } }, // ORDER BY
{ $limit: 10 }
])
Outros estágios: $project (escolher/calcular campos), $unwind (desmembrar uma lista em um documento por item), $lookup (equivalente a um
JOIN entre collections), $addFields, $facet. O antigo Map Reduce (a ideia de dividir o trabalho em map e reduce para processar em
paralelo, como fazer vários cachorros-quentes ao mesmo tempo) foi substituído pelo aggregation framework e está obsoleto. O
MongoDB Compass monta pipelines de forma visual.
Busca geoespacial¶
Com coordenadas guardadas num campo (de preferência no formato GeoJSON), cria-se um índice "2dsphere" (ou "2d" para plano) e
consultam-se os documentos perto de um ponto ($near, com $maxDistance) ou dentro de uma área ($geoWithin) — o caso de "municípios
vizinhos" ou "lojas mais próximas".
Índices e desempenho¶
Sem índice, uma consulta lê a collection inteira (COLLSCAN). O comando explain("executionStats") mostra o plano de execução:
nReturned (retornados), totalDocsExamined (lidos), totalKeysExamined (chaves de índice lidas) e executionTimeMillis. Uma consulta saudável
tem IXSCAN e totalDocsExamined próximo de nReturned. No exemplo do livro, criar um índice no campo de ano derrubou a consulta de
cerca de 1,8 s para 0,13 s (e de 1,3 milhão de documentos lidos para 13 mil).
| Índice | Uso |
|---|---|
Simples createIndex({ ano: 1 }) |
Filtros e ordenação por um campo (1 = crescente, -1 = decrescente; a direção só importa em compostos) |
Composto createIndex({ ano: 1, nota: -1 }) |
Consultas por vários campos; a ordem dos campos importa |
| De array (multikey) | Indexa cada elemento de uma lista (categorias) |
| Em campo aninhado | Indexe o caminho completo (atores.sexo); indexar só o campo pai não ajuda a consultar o filho |
Único (unique: true) |
Impede duplicatas (falha se já existirem; um nome de município repete em vários estados, então o único é por nome + estado) |
TTL (expireAfterSeconds) |
Apaga documentos automaticamente após um tempo (logs, sessões) usando um campo de data |
| Parcial | Indexa só os documentos que atendem a um filtro (menor e mais barato) |
| Texto | Busca textual por palavras (full text search) |
O índice de _id não pode ser removido. Índices custam espaço e tornam a escrita mais lenta: crie só os que as consultas usam. Desde a versão
4.2 a criação de índices não bloqueia mais o banco, o que tornou a opção de criar "em background" desnecessária. Mesmo com bons índices, nada supera um bom
schema design. Veja o princípio geral em SQL.
Administração¶
- Armazenamento: o motor padrão WiredTiger (desde a versão 3) gerencia compressão e memória sozinho; o MongoDB usa a memória disponível
como cache (o ideal é o working set caber na RAM).
compactreorganiza o espaço de uma collection. - Scripts: o shell tem um interpretador JavaScript (variáveis, laços, funções), útil para rotinas de DBA, como listar o
statsde cada collection. As funções JavaScript armazenadas no servidor (stored functions) são limitadas e desencorajadas; prefira o pipeline de agregação. - Segurança: por padrão a autenticação vem desligada (facilidade de uso). Em produção, habilite autenticação (usuários e senhas), autorização por papéis (role-based, por banco e collection), TLS e restrição de rede. Veja Administração de banco.
Replica set e sharding¶
flowchart LR
APP[Aplicação] --> R[mongos<br/>query router]
R --> S1[(Shard 1<br/>replica set)]
R --> S2[(Shard 2<br/>replica set)]
R --> S3[(Shard 3<br/>replica set)]
R -.metadados.-> C[(Config servers<br/>replica set)]
- Replica set: um nó primário recebe as escritas; os secundários replicam. Os nós trocam heartbeats a cada 2 segundos e, se o primário
cai, os demais elegem um novo (por isso, de preferência, um número ímpar de nós/árbitro). Nós em máquinas diferentes dão alta disponibilidade de fato
(rodar vários nós no mesmo servidor só serve para teste). Comandos:
rs.initiate(),rs.add(),rs.status(). - Sharding: quando uma collection chega a bilhões de documentos e não cabe em um servidor, ela é dividida por uma chave de partição (shard key) entre vários shards (cada um um replica set). Três componentes: shards (dados), config servers (metadados) e mongos (roteador de consultas). A escolha da shard key é decisiva (boa distribuição e cardinalidade, evitar chaves monotônicas como data crescente, que concentram as escritas num shard). Detalhes de particionamento em Administração de banco.
Transações¶
O MongoDB nasceu com atomicidade por documento: uma gravação em um documento (mesmo com subdocumentos) é atômica, e o modelo embutido procura aproveitar isso, evitando transações. Desde a versão 4.0 há transações ACID multidocumento (em replica sets e, depois, em clusters particionados):
const sessao = db.getMongo().startSession()
sessao.startTransaction()
const contas = sessao.getDatabase("banco").contas
contas.updateOne({ _id: "A" }, { $inc: { saldo: -100 } })
contas.updateOne({ _id: "B" }, { $inc: { saldo: 100 } })
sessao.commitTransaction() // ou abortTransaction()
Dentro da transação, outras sessões só veem os dados depois do commit. Se duas transações alteram o mesmo documento, a segunda falha
com WriteConflict e a aplicação deve tratar e tentar de novo. Transações têm custo; no MongoDB o ideal é ainda modelar para precisar delas o mínimo.
Nota
A fonte, de uma versão antiga, afirma que o MongoDB "não suporta transação" e "não substitui um banco relacional". Hoje a primeira afirmação não vale mais (veja acima); a segunda continua sendo uma questão de adequação: a escolha entre eles está em Como escolher o banco.
Limites úteis¶
Documento: 16 MB, até 100 níveis de aninhamento; collection: até 64 índices; nomes de campo, collection e database têm limites de tamanho. O nome "Mongo" vem de humongous (enorme).
Como escolher o banco¶
flowchart TD
A["Dados: estrutura, volume,<br/>relacionamentos, crescimento"] --> E["Escolha"]
B["Consultas: leituras e escritas<br/>mais importantes"] --> E
C["Consistência: exata agora<br/>ou pode atualizar depois?"] --> E
D["Escala e equipe: picos, regiões,<br/>quem opera e mantém"] --> E
- Dados: analise estrutura, volume, relacionamentos e velocidade de crescimento.
- Consultas: liste as leituras e escritas mais importantes antes de modelar.
- Consistência: defina se o dado precisa ser exato imediatamente ou pode atualizar depois.
- Escala: considere usuários, picos, regiões, disponibilidade e necessidade de distribuição.
- Equipe: escolha a tecnologia que a equipe consiga operar, monitorar e manter.
Regra de ouro
Comece simples, meça a necessidade real e evite tecnologia só por moda. Em muitos sistemas, um banco relacional bem modelado resolve; vários produtos reais combinam mais de um tipo (ex.: relacional para pedidos + chave-valor para cache + busca textual para o catálogo).