Tecnologias/02 - Bancos de Dados/02 - Não Relacionais/01 - MongoDB6 min
MongoDB
Conceito
MongoDB guarda documentos BSON (JSON binário, com tipos: ObjectId, Date,
Decimal128, binário) em coleções, sem schema obrigatório.
A tese não é "sem schema" — é localidade:
Um pedido, com seus itens, endereço de entrega e status, é um documento. Uma leitura de disco traz a coisa inteira. Em modelo relacional normalizado, o mesmo pedido são 4 tabelas e 3 joins.
O schema não desapareceu; ele saiu do banco e foi para a aplicação (e, se você quiser
de volta, para o $jsonSchema de validação). Essa é a troca central, e vale tanto de
elogio quanto de crítica.
Arquitetura em uma tela
mongodé o processo do servidor. WiredTiger é o motor de armazenamento: concorrência em nível de documento, snapshots MVCC, compressão (Snappy/Zstd) por padrão.- Replica set: 1 primário + N secundários. Só o primário aceita escrita; os secundários replicam pelo oplog (uma capped collection). Eleição automática em ~10s por um protocolo tipo Raft. É a unidade mínima de produção — nunca rode um
mongodsolitário. Ver 3. Transações, Replica Set e Durabilidade. - Sharding:
mongos(roteador) + config servers + N shards, cada um um replica set. A shard key decide a distribuição — e é a decisão mais difícil de reverter no MongoDB. - Aggregation pipeline: estágios encadeados (
$match,$group,$sort,$lookup,$unwind,$facet) — é o "SQL" do Mongo, e é bem mais capaz do que a fama sugere. - Change streams: fluxo de mudanças em tempo real sobre o oplog. É o
CDCnativo, e o caminho natural para alimentar um tópico Kafka.
Índices
Não é um detalhe: no MongoDB, consulta sem índice não é lenta, é uma varredura da coleção inteira — e o padrão silencioso é permitir isso.
| Tipo | Para quê |
|---|---|
| single field / compound | o essencial |
| multikey | criado automaticamente ao indexar um array |
| partial | só documentos que satisfazem um filtro |
| TTL | expiração automática de documento (sessão, cache, log) |
| text / wildcard | busca textual; campos de nome desconhecido |
geo (2dsphere) |
consulta geoespacial |
| vector (Atlas) | busca semântica / RAG |
A regra prática para índice composto é ESR: campos de Equality primeiro, depois
Sort, depois Range. Um índice {status: 1, criado_em: -1} serve
find({status}).sort({criado_em: -1}) sem ordenação em memória; na ordem inversa, não.
Ver 5. Índices.
Versões
MongoDB 8.0 é a major atual; 8.1, 8.2 e 8.3 são rapid releases. A 8.3 (GA em
mai/2026) reporta até 45% mais leitura, 35% mais escrita e 15% melhor desempenho de
transação ACID em relação à 8.0. A empresa também empurrou o produto para IA — depois da
compra da Voyage AI (fev/2025), embeddings e reranking passaram a fazer parte da
plataforma, com $vectorSearch e $rerank no pipeline.
Trade-offs
Não há integridade referencial. Não existe foreign key, ON DELETE CASCADE nem
constraint entre coleções. Se o pedido referencia um cliente apagado, o banco não
reclama. A consistência do grafo de dados é responsabilidade da aplicação — sempre.
$lookup não é JOIN. Ele existe desde a 3.2 e melhorou muito, mas não tem o
otimizador de join de um relacional: sem índice no campo estrangeiro, é uma varredura
por documento da coleção externa. Um pipeline com dois $lookup sobre coleções grandes é
o antipadrão de performance nº 1 — e o sinal de que a modelagem está relacional demais
(2. Modelagem de Documentos).
Limite de 16 MB por documento. Parece generoso e é o teto que mata: qualquer array que cresce sem limite (comentários de um post, eventos de um dispositivo, histórico de uma conta) eventualmente estoura, e a correção é uma remodelagem com o sistema em produção.
Consulta ad-hoc é fraca. "Quantos clientes de SP compraram produto da categoria X no Q2, agrupado por canal?" é trivial em SQL e trabalhosa aqui — porque o schema foi desenhado para outras perguntas. Para análise, o caminho é exportar para um armazém ou usar o Atlas Data Federation, não brigar com o pipeline.
Shard key é quase irreversível. Desde a 5.0 é possível refinar e até reshardar, mas em coleção grande é uma operação longa e delicada. Escolher errado (um campo de baixa cardinalidade, ou monotônico como timestamp) concentra a carga num shard — o hot shard.
O trade-off central: MongoDB entrega velocidade de desenvolvimento e escala horizontal de escrita em troca de garantias de schema e liberdade de consulta. Quando o padrão de acesso é conhecido e estável, é uma troca excelente. Quando o dado é muito relacionado e as perguntas mudam, é o pior dos dois mundos.
Exemplo prático
Um documento de pedido — o mesmo dado que seriam 4 tabelas:
{
_id: ObjectId("..."),
numero: "PED-2026-0917",
cliente: { // extended reference: só o que a tela mostra
_id: ObjectId("..."),
nome: "Ana Souza",
email: "ana@exemplo.com"
},
itens: [ // 1-para-poucos: EMBUTIDO
{ sku: "A1", nome: "Teclado", qtd: 1, preco: Decimal128("249.90") },
{ sku: "B7", nome: "Mouse", qtd: 2, preco: Decimal128("89.90") }
],
total: Decimal128("429.70"), // pré-calculado: computed pattern
status: "pago",
criado_em: ISODate("2026-09-08T14:12:00Z")
}
// índice composto seguindo ESR: Equality (status) → Sort (criado_em)
db.pedidos.createIndex({ status: 1, criado_em: -1 })
// a consulta da tela: uma leitura, zero join
db.pedidos.find({ status: "pago" }).sort({ criado_em: -1 }).limit(20)
// relatório: agregação por dia
db.pedidos.aggregate([
{ $match: { criado_em: { $gte: ISODate("2026-09-01") } } },
{ $group: { _id: { $dateTrunc: { date: "$criado_em", unit: "day" } },
receita: { $sum: "$total" }, pedidos: { $sum: 1 } } },
{ $sort: { _id: 1 } }
])
Duas decisões de projeto estão visíveis aí, e as duas são deliberadas:
clienteestá duplicado dentro do pedido. Se a Ana trocar de e-mail, os pedidos antigos guardam o antigo — o que para um pedido é correto (é um registro histórico), e para um perfil seria errado. Ver Denormalization.totalestá pré-calculado. Some os itens toda vez e você paga na leitura; grave o total e você paga em consistência. Como pedido é imutável depois de pago, é barato.
Como isso falha em produção
| Sintoma | Causa |
|---|---|
| Query lenta e CPU alta no primário | COLLSCAN: falta índice. Confirme com .explain("executionStats") |
Memória estourando no aggregate |
$group/$sort além de 100 MB sem allowDiskUse |
BSONObjectTooLarge |
array crescendo sem limite → 16 MB. Ver bucket pattern |
| Um shard com 90% da carga | shard key monotônica (timestamp, ObjectId) ou de baixa cardinalidade |
| Leitura devolvendo dado velho | readPreference: secondary com réplica atrasada (3. Transações, Replica Set e Durabilidade) |
| Escrita "perdida" após failover | writeConcern: 1 — o primário confirmou e caiu antes de replicar |
| Réplica precisando de resync total | janela do oplog menor que o tempo que ela ficou fora |
| Dado inconsistente entre documentos | ausência de validação: $jsonSchema não foi configurado |
Aprofundamento aplicado
Atualização parcial e condição na mesma operação
Ler um documento inteiro, modificar um campo na aplicação e substituir tudo pode sobrescrever alterações independentes. Operadores de atualização reduzem o escopo; um filtro com a versão esperada também detecta concorrência sobre a mesma decisão.
// mongosh; coleção de estudo
db.estoque_estudo.insertOne({ _id: "p1", saldo: 1 });
db.estoque_estudo.updateOne(
{ _id: "p1", saldo: { $gte: 1 } },
{ $inc: { saldo: -1 } }
);
Verifique matchedCount para saber se a condição encontrou um documento. A atomicidade
individual não transforma várias atualizações de documentos em uma transação conjunta.
Referência: atomicidade de escritas.
Exercício: execute a reserva duas vezes sobre saldo inicial 1. Resultado esperado: a primeira encontra o documento; a segunda não encontra saldo suficiente. Repita concorrentemente e preserve a mesma propriedade.
Relacionado
- 2. Modelagem de Documentos
- 3. Transações, Replica Set e Durabilidade
- 4. MongoDB vs Relacional
- Document Store
- SQL vs NoSQL
- Sharding
- DynamoDB
Parte de MOC MongoDB · mongodb.com/docs · Notas da 8.3