MongoDB: Documentos e BSON: a base dos dados do MongoDB
Última atualização: 2026-08-26
O BSON é o formato de dados do MongoDB — ele amplia os recursos do JSON e oferece suporte a tipos nativos, como Date, Binary e Decimal128.
Este curso oferece uma compreensão aprofundada do formato de dados BSON, da estrutura interna do ObjectId e do sistema de tipos de campo, além de ensinar as melhores práticas para o projeto de documentos.
1. O que você vai aprender
- A diferença fundamental entre o formato de dados BSON e o JSON
- A estrutura interna dos documentos do MongoDB (_id, campos, valores)
- A composição do ObjectId, a extração do carimbo de data e hora e a garantia de exclusividade
- 12 tipos de dados BSON (String, Number, Date, Array, Object, ObjectId, etc.)
- Convenções de nomenclatura de campos (CamelCase x SnakeCase x Kebab-Case)
- A filosofia de design por trás do limite de tamanho do documento (16 MB)
- Estratégias para escolher entre documentação incorporada e citações
2. A história real de um engenheiro full-stack
(1) Problema: As datas são convertidas em strings quando o JSON é armazenado no MongoDB
Charlie é um engenheiro full-stack de Node.js que está migrando dados do MySQL para o MongoDB:
“Converti os dados dos pedidos do MySQL para JSON e os armazenei no MongoDB, mas descobri que todas as datas haviam sido convertidas para a string
new Date(), que não podia ser analisada; a precisão dos valores foi perdida (0,1 + 0,2 ≠ 0,3); e as fotos de perfil em formato binário não puderam ser armazenadas de forma alguma.”
Ele serializou os dados do pedido usando JSON.stringify(), o que resultou na perda de informações de tipo:
// ❌ Error:JSON.stringify Type of Loss
const order = {
createdAt: new Date(), // Date Object
total: new Number('0.30'), // Decimal128 A more precise type should be used.
avatar: Buffer.from('...'), // Binary Avatar
_id: new ObjectId() // MongoDB Expected ObjectId
};
const json = JSON.stringify(order);
// {"createdAt":"2026-07-01T...","total":0.3,"avatar":"...","_id":"..."}
// ^^^^^^^^^^^^^^^^ String ^ Floating-point numbers(Loss of Accuracy) ^ String(Cannot be restored)
(2) A solução BSON
O MongoDB armazena dados diretamente no formato BSON (JSON binário), preservando todas as informações de tipo.
// ✅ Correct:mongoose Direct Operation BSON Type
const OrderSchema = new mongoose.Schema({
createdAt: { type: Date, default: Date.now }, // BSON Date
total: { type: mongoose.Schema.Types.Decimal128 }, // BSON Decimal128(Accurate)
avatar: { type: Buffer }, // BSON Binary
_id: { type: mongoose.Schema.Types.ObjectId, auto: true } // BSON ObjectId
});
const order = await Order.create({
total: mongoose.Types.Decimal128.fromString('0.30'),
// TODO: 替换为实际头像文件路径
avatar: fs.readFileSync('avatar.jpg')
});
(3) Receita
| Dimensão | JSON | BSON |
|---|---|---|
| Tipo de data | String (requer análise manual) | Data nativa (precisão em milissegundos) |
| Precisão numérica | Ponto flutuante (perda de precisão) | Decimal128 (precisão de 34 bits) |
| Dados binários | Não compatível | Binário nativo |
| Ordem dos campos | Sem ordem | Em ordem (Importante!) |
| Tamanho e uso de recursos | Mais compacto | Ligeiramente maior (5–15% maior) |
3. Formato de dados BSON
Visão geral do conceito: O BSON (Binary JSON) é um formato de serialização binário específico do MongoDB e constitui um superconjunto do JSON. Enquanto o JSON possui apenas seis tipos de dados (string, número, booleano, nulo, array e objeto), o BSON suporta mais de 12 tipos, incluindo tipos essenciais para bancos de dados, como Date, Binary, ObjectId e Decimal128. As principais vantagens do BSON são: tipos de dados abrangentes, campos ordenados e análise extremamente rápida.
Como funciona: Os documentos BSON são armazenados em formato binário. Cada documento começa com um cabeçalho de comprimento de 4 bytes, seguido por uma sequência de pares chave-valor, e termina com 0x00. Ao contrário da análise baseada em texto do JSON, o cabeçalho de comprimento do BSON permite pular rapidamente campos desnecessários (semelhante ao design de “cabeçalho de comprimento fixo” em protocolos binários), resultando em um desempenho de análise de 3 a 5 vezes mais rápido que o do JSON. A desvantagem é um aumento de 5 a 15% na sobrecarga de espaço (para armazenar informações de tipo e comprimento).
graph TB
subgraph "BSON Internal Structure of the Document"
A[4 Byte<br/>Total length of the document] --> B[Type Code 1B<br/>+ Field Name<br/>+ Value]
B --> C[Type Code 1B<br/>+ Field Name<br/>+ Value]
C --> D[...More key-value pairs...]
D --> E[0x00<br/>Closing tag]
end
style A fill:#cce5ff
| Dimensão | JSON | BSON |
|---|---|---|
| Tipo | Formato de texto | Formato binário |
| Legibilidade | ✅ Legível por humanos | ❌ Binário |
| Desempenho | Resolução lenta | Resolução extremamente rápida (3–5x) |
| Grande variedade | 6 tipos | Mais de 12 tipos |
| Ordem de campos | Sem ordem | Ordenado |
| Espaço | Mais compacto | 5–15% a mais |
(1) O que é BSON?
O BSON (Binary JSON) é o formato de serialização binária utilizado pelo MongoDB. Suas características incluem:
graph LR
A[JavaScript Object] -->|JSON.stringify| B[JSON Text]
A -->|BSON Serialization| C[BSON Binary]
B --> D[Transmission / Storage]
C --> D
style C fill:#d4edda
| Dimensão | JSON | BSON |
|---|---|---|
| Tipo | Formato de texto | Formato binário |
| Legibilidade | ✅ Legível por humanos | ❌ Binário |
| Desempenho | Resolução lenta | Resolução extremamente rápida |
| Grande variedade | 6 tipos | Mais de 12 tipos |
| Ordem de campos | Sem ordem | Ordenado |
| Espaço | Mais compacto | 5–15% a mais |
(2) Estrutura do documento BSON
Análise dos pontos-chave:
- Ordem de inserção dos campos BSON — isso é fundamental para a indexação e a otimização de consultas no MongoDB
- Cada campo é precedido por um código de tipo de 1 byte, o que permite que o BSON distinga entre Date e String (ao contrário do JSON).
- Documentos e matrizes aninhados são armazenados de forma recursiva no BSON, com uma profundidade máxima de aninhamento de 100 níveis.
- O campo
_idestá sempre no início do documento, o que otimiza o desempenho das consultas
// One BSON Internal Representation of a Document(Simplify)
{
_id: ObjectId("507f1f77bcf86cd799439011"), // 12 Byte ObjectId
name: "Alice", // String(UTF-8)
age: 28, // Int32
balance: Decimal128("12345.6789"), // Decimal128(High precision)
joinedAt: ISODate("2026-07-01T10:00:00Z"), // Date(64-bit Integer)
isActive: true, // Boolean
hobbies: ["reading", "coding", "hiking"], // Array
address: { // Embedded Document
city: "Tokyo",
country: "Japan"
},
profile: null, // Null
avatar: BinData(0, "..."), // Binary
// Field Order:BSON Preserve the insertion order of fields(JSON No guarantee)
}
▶ Exemplo 1: Visualizando detalhes do BSON no mongosh
// Insert a document
db.users.insertOne({
name: "Alice",
age: 28,
joinedAt: new Date(),
balance: NumberDecimal("12345.6789"),
address: { city: "Tokyo", country: "Japan" }
});
// View BSON Details(Usage bsonSon Function)
db.users.findOne({ name: "Alice" });
// {
// _id: ObjectId('507f1f77bcf86cd799439011'),
// name: 'Alice',
// age: 28,
// joinedAt: ISODate('2026-07-01T10:00:00.000Z'),
// balance: NumberDecimal('12345.6789'),
// address: { city: 'Tokyo', country: 'Japan' }
// }
// View Field Types
const doc = db.users.findOne({ name: "Alice" });
print(typeof doc.age); // number
print(doc.joinedAt instanceof Date); // true
4. O mecanismo de chave primária ObjectId
Explicação do conceito: ObjectId é o tipo de chave primária padrão do MongoDB, consistindo em um valor binário de 12 bytes (96 bits). Ao contrário das chaves primárias inteiras com autoincremento encontradas em bancos de dados tradicionais, o ObjectId emprega um design distribuído — composto por um timestamp, um valor aleatório e um contador —, garantindo a exclusividade global sem a necessidade de coordenação centralizada. Outra grande vantagem do ObjectId é que ele inclui, por natureza, a data e hora de criação, que podem ser extraídas diretamente sem a necessidade de campos adicionais.
Como funciona: Os 12 bytes de um ObjectId são divididos em três segmentos: os primeiros 4 bytes correspondem a um timestamp do Unix (com precisão de segundos), os 5 bytes do meio são um valor aleatório (determinado pelo ID da máquina e pelo ID do processo no momento da geração inicial, permanecendo inalterado a partir de então) e os últimos 3 bytes são um contador incremental (que aumenta a partir de um valor inicial aleatório dentro do mesmo segundo). Esse design permite que um único processo gere aproximadamente 16,77 milhões de ObjectIds exclusivos em um único segundo.
graph LR
A[ObjectId 12 Byte] --> B[4 Byte Timestamp<br/>Accuracy to the second]
A --> C[5 Random Byte Values<br/>Machine/Unique Process]
A --> D[3 Byte-Increment Counter<br/>Unique within a single second]
style A fill:#cce5ff
| Seção | Extensão | Conteúdo | Objetivo |
|---|---|---|---|
| Carimbo de data/hora | 4 bytes | Carimbo de data/hora Unix (segundos) | É possível extrair a data de criação |
| Aleatório | 5 bytes | ID da máquina + ID do processo | Único entre os processos |
| Contador | 3 bytes | Contador incremental | Único dentro de um único segundo |
| _id Estratégia | Vantagens | Desvantagens | Casos de uso |
|---|---|---|---|
| ObjectId gerado automaticamente | Distribuído, único, com registro de data e hora, ordenado naturalmente | 12 bytes (relativamente grande) | De uso geral (padrão) |
| Chaves de negócios em forma de string | Semântica clara, boa legibilidade | É preciso garantir manualmente que sejam únicas | Número do pedido, SKU |
| Inteiros com autoincremento | Compacto, legível | Requer a definição de um contador; não é adequado para particionamento | Sistemas legados |
| UUID | Exclusivo globalmente | 16 bytes, sem ordem definida | Exclusivo entre sistemas |
(1) O que é um ObjectId?
ObjectId é o tipo padrão de chave primária do MongoDB, um valor binário de 12 bytes (96 bits):
graph LR
A[ObjectId 12 Byte] --> B[4 Byte Timestamp<br/>Accuracy to the second]
A --> C[5 Random Byte Values<br/>Machine/Unique Process]
A --> D[3 Byte-Increment Counter<br/>Unique within a single second]
style A fill:#cce5ff
| Seção | Extensão | Conteúdo | Objetivo |
|---|---|---|---|
| Carimbo de data/hora | 4 bytes | Carimbo de data/hora Unix (segundos) | É possível extrair a data de criação |
| Aleatório | 5 bytes | ID da máquina + ID do processo | Único entre os processos |
| Contador | 3 bytes | Contador incremental | Único dentro de um único segundo |
(2) Vantagens do ObjectId
Análise dos pontos-chave:
- A parte do ObjectId correspondente ao carimbo de data/hora classifica naturalmente as entradas por hora de inserção — permitindo consultas por intervalo de tempo sem a necessidade de índices
createdAtadicionais. - Um valor aleatório de 5 bytes é gerado e armazenado em cache quando o processo é iniciado, garantindo a exclusividade entre os processos (2^40 ≈ 1 trilhão de possibilidades).
- O contador de 3 bytes é incrementado a cada segundo, gerando 2^24 ≈ 16,77 milhões de IDs únicos por segundo.
- O método
getTimestamp()permite extrair a data de criação diretamente do ObjectId, sem a necessidade de consultas adicionais.
// Create ObjectId in mongosh
const id1 = ObjectId(); // Automatically Generated
const id2 = ObjectId("507f1f77bcf86cd799439011"); // Generate from a string
// Retrieve the creation time(Key Advantages!)
id2.getTimestamp();
// ISODate("2012-10-17T20:46:11.000Z")
// Use in Node.js with mongoose
const mongoose = require('mongoose');
const id = new mongoose.Types.ObjectId();
console.log(id.getTimestamp()); // 2026-07-01T10:00:00.000Z
(3) Garantindo a exclusividade do ObjectId
graph TB
A[Client A<br/>Generated in the same second ID] --> A1[time=1000<br/>random=ABC<br/>counter=1]
A --> A2[time=1000<br/>random=ABC<br/>counter=2]
A --> A3[time=1000<br/>random=ABC<br/>counter=3]
B[Client B<br/>Generated in the same second ID] --> B1[time=1000<br/>random=DEF<br/>counter=1]
B --> B2[time=1000<br/>random=DEF<br/>counter=2]
style A1 fill:#d4edda
style B1 fill:#d4edda
▶ Exemplo 2: Extração do carimbo de data e hora do ObjectId
// === In mongosh ===
const products = db.products.find().toArray();
products.forEach(p => {
print(`Product ${p._id} created at ${p._id.getTimestamp()}`);
});
// === by ObjectId Time Range Query ===
const startOfDay = ObjectId.createFromTime(
Math.floor(new Date('2026-07-01').getTime() / 1000)
);
const endOfDay = ObjectId.createFromTime(
Math.floor(new Date('2026-07-02').getTime() / 1000)
);
db.products.find({
_id: { $gte: startOfDay, $lt: endOfDay }
});
// === Node.js / mongoose ===
const Product = mongoose.model('Product', productSchema);
const products = await Product.find({
_id: {
$gte: mongoose.Types.ObjectId.createFromTime(
Math.floor(Date.parse('2026-07-01') / 1000)
),
$lt: mongoose.Types.ObjectId.createFromTime(
Math.floor(Date.parse('2026-07-02') / 1000)
)
}
});
5. Tipos de dados BSON
Explicação do conceito: O BSON suporta mais de 12 tipos de dados, superando em muito os 6 do JSON. A diferença mais significativa está nos tipos numéricos — o JSON possui apenas um tipo numérico (Number, que é um número de ponto flutuante de precisão dupla IEEE 754), enquanto o BSON oferece quatro tipos numéricos: Double, Int32, Int64 (Long) e Decimal128. A escolha do tipo numérico incorreto pode resultar em perda de precisão (por exemplo, em cálculos monetários 0.1 + 0.2 ≠ 0.3).
Casos de uso: Valores financeiros devem usar Decimal128 (precisão decimal de 34 dígitos); contadores usam Int32; IDs inteiros grandes usam Long; e cálculos científicos e estatísticas usam Double. O tipo Date é armazenado em BSON como um carimbo de data/hora de 64 bits em milissegundos, o que é fundamentalmente diferente das datas baseadas em strings do JSON.
| Tipo | Código do tipo | Exemplo | Finalidade |
|---|---|---|---|
| Duplo | 1 | 3.14, 0.1+0.2 |
Ponto flutuante (número padrão) |
| String | 2 | "Alice" |
String UTF-8 |
| Objeto | 3 | { key: "value" } |
Documento aninhado |
| Matriz | 4 | [1, 2, 3] |
Matriz |
| Dados binários | 5 | BinData(0, "...") |
Dados binários (imagens, arquivos) |
| Não definido | 6 | undefined |
Não recomendado |
| ObjectId | 7 | ObjectId("...") |
Chave primária padrão |
| Booleano | 8 | true, false |
Booleano |
| Data | 9 | ISODate("...") |
Data e hora |
| Nulo | 10 | null |
Valor vazio |
| Expressão regular | 11 | /pattern/i |
Expressão regular |
| Inteiro de 32 bits | 16 | NumberInt(123) |
Inteiro de 32 bits |
| Inteiro de 64 bits | 18 | NumberLong(123) |
Inteiro de 64 bits (BigInt) |
| Decimal128 | 19 | NumberDecimal("0.30") |
Decimais de alta precisão (finanças) |
| MinKey/MaxKey | -1 / 127 | MinKey(), MaxKey() |
Limite de comparação |
(1) 12 tipos de dados BSON
(2) Como selecionar um tipo numérico
Explicação do conceito: A escolha do tipo de dados numéricos é a decisão mais crítica ao se trabalhar com tipos de dados BSON. O JSON possui apenas um tipo Number (ponto flutuante de precisão dupla), o que leva ao clássico problema da perda de precisão em cálculos financeiros: 0.1 + 0.2 = 0.30000000000000004. O tipo Decimal128 do BSON resolve esse problema ao oferecer precisão decimal de 34 bits, tornando-o adequado para cenários que exigem cálculos precisos, como valores monetários e alíquotas de impostos.
| Tipo numérico | Precisão | Intervalo | Tamanho de armazenamento | Casos de uso |
|---|---|---|---|---|
| Duplo | 15–17 dígitos significativos | ±1,7×10³⁰⁸ | 8 bytes | Computação científica, estatística, gráficos |
| Int32 | Exato | -2^31 ~ 2^31-1 | 4 bytes | Contagem de uso geral, inventário |
| Int64/Long | Precisão | -2^63 ~ 2^63-1 | 8 bytes | IDs inteiros grandes, carimbos de data/hora |
| Decimal128 | decimal de 34 bits | ±10^6145 | 16 bytes | Valor financeiro (recomendado) |
graph TB
A[MongoDB Numeric Types] --> B[Double<br/>Default]
A --> C[Int32<br/>32 Integer]
A --> D[Long<br/>64 Integer]
A --> E[Decimal128<br/>34 Decimal place]
B --> B1[Applicable:Scientific Computing、Statistics]
C --> C1[Applicable:Routine Count]
D --> D1[Applicable:Large integers ID]
E --> E1[Applicable:Finance、Amount]
style E fill:#d4edda
▶ Exemplo 3: Trabalhando com tipos numéricos
// === Double(Default)===
db.products.insertOne({
sku: "PHONE-001",
price: 599.99 // Save as Double
});
// === Decimal128(Financial Recommendations)===
db.accounts.insertOne({
balance: NumberDecimal("1234567890.12345678901234567890")
// Precise Storage,No loss of precision
});
// === Int32(Count)===
db.products.insertOne({
sku: "BOOK-001",
stock: NumberInt(150)
});
// === Long(Large integers ID)===
db.orders.insertOne({
_id: NumberLong("1700000000000") // Timestamps as ID
});
// === JavaScript Processing Decimal128 ===
const account = await Account.findOne({});
console.log(account.balance.toString()); // "1234567890.12345678901234567890"
// === Number The Precision Trap ===
0.1 + 0.2; // 0.30000000000000004 ❌
NumberDecimal("0.1") + NumberDecimal("0.2"); // NumberDecimal("0.3") ✅
6. Convenções de nomenclatura de campos
Explicação do conceito: A nomenclatura dos campos pode parecer um detalhe menor, mas tem um impacto significativo na colaboração da equipe e na manutenção a longo prazo. O MongoDB impõe três restrições rígidas aos nomes de campos (eles não podem começar com $, não podem conter . e não podem ser uma string vazia), além de várias recomendações não obrigatórias (recomenda-se o uso de camelCase, evitar palavras reservadas e limitar o comprimento). Uma convenção de nomenclatura consistente é a base da facilidade de manutenção do banco de dados.
Cenários de uso: O ecossistema JavaScript/TypeScript recomenda o uso de camelCase (em consonância com os nomes das variáveis de código), enquanto o ecossistema Python/SQL recomenda o uso de snake_case (em consonância com os nomes das colunas do banco de dados). Na pilha tecnológica MongoDB + Mongoose, recomenda-se o uso de camelCase para os nomes dos campos do banco de dados, com a conversão para snake_case na camada de API por meio da conversão toJSON do Mongoose.
| Estilo de nomenclatura | Exemplo | Vantagens | Desvantagens | Recomendação |
|---|---|---|---|---|
| camelCase | firstName |
Suporte nativo em JS/TS | Não é compatível com SQL | ⭐⭐⭐ (Recomendado) |
| snake_case | first_name |
Compatível com SQL/Python | Precisa de aspas em JS | ⭐⭐ |
| kebab-case | first-name |
Compatível com URLs | Requer aspas no MongoDB | ⭐ |
(1) Convenções de nomenclatura de campos no MongoDB
✅ Nomeação válida:
- Os nomes dos campos não podem começar com
$(palavra reservada) - Os nomes dos campos não podem conter
.(a notação por pontos é mantida) - Os nomes dos campos não podem ser strings vazias
""
// ✅ Valid field names
db.users.insertOne({
firstName: "Alice", // Hump-style
first_name: "Alice", // Snake-like
"first-name": "Alice", // kebab-case(Quotation marks are required)
"user 1": "Alice", // Contains spaces(Quotation marks are required)
age28: 28 // Ending in a number
});
// ❌ Invalid field name
db.users.insertOne({
$name: "Alice", // starts with $ ❌
"user.name": "Alice", // contains . ❌
"": "Alice" // Empty string ❌
});
(2) Comparação entre três convenções de nomenclatura
| Estilo | Exemplo | Vantagens | Desvantagens |
|---|---|---|---|
| camelCase | firstName |
Suporte nativo em JS/TS | Não é compatível com SQL |
| snake_case | first_name |
Compatível com SQL/Python | Precisa de aspas no JS |
| kebab-case | first-name |
Compatível com URLs | Requer aspas no MongoDB |
(3) Recomendação: camelCase + estilo oficial do MongoDB
// ✅ Recommended Styles:camelCase
db.users.insertOne({
firstName: "Alice",
lastName: "Smith",
emailAddress: "alice@example.com",
dateOfBirth: new Date("1998-01-01"),
isActive: true,
totalSpent: NumberDecimal("1234.56")
});
▶ Exemplo 4: Convenções de nomenclatura de esquemas do Mongoose
// mongoose Automatically convert camelCase to database fields
const UserSchema = new mongoose.Schema({
firstName: { type: String, required: true }, // Database Fields:firstName
emailAddress: { type: String, required: true }, // Database Fields:emailAddress
createdAt: { type: Date, default: Date.now }, // Database Fields:createdAt
isActive: { type: Boolean, default: true } // Database Fields:isActive
});
// Through toJSON Convert Underscore-Based Naming Conventions(API On the way back)
UserSchema.set('toJSON', {
virtuals: true,
versionKey: false,
transform: (doc, ret) => {
ret.first_name = ret.firstName;
delete ret.firstName;
return ret;
}
});
7. Limites de tamanho dos documentos
Explicação do conceito: O tamanho máximo de um único documento no MongoDB é de 16 MB, e a profundidade máxima de aninhamento é de 100 níveis. Essa limitação é uma filosofia central de design do MongoDB — ela incentiva a incorporação de dados relacionados em um único documento (para evitar JOINs), mas desestimula o armazenamento de documentos extremamente grandes. O limite de 16 MB permite que o MongoDB processe documentos individuais com eficiência na memória, garantindo tempos de resposta rápidos para consultas e atualizações.
Como funciona: A razão por trás do limite de 16 MB é que o mecanismo de armazenamento WiredTiger do MongoDB utiliza uma estratégia de “atualização no local” ao modificar documentos — se o documento ficar maior após a atualização e não houver espaço suficiente em seu local original, ele deverá ser movido para um novo local, o que aciona uma atualização do índice (todas as entradas do índice que apontam para esse documento devem ser atualizadas). Quanto maior o documento, maior o custo de movê-lo. Portanto, o MongoDB escolheu 16 MB como ponto de equilíbrio.
| Dimensão | Restrição | Motivo |
|---|---|---|
| Tamanho de um único documento | 16 MB | Tamanho máximo do documento BSON |
| Profundidade de aninhamento | 100 níveis (padrão) | Evita estouro de pilha |
| Comprimento do nome do campo | 255 bytes | Codificação UTF-8 |
| Número de índices | 64 por coleção | Tamanho dos metadados do índice |
| Comprimento total de uma única chave de índice agrupado | 1024 bytes | Eficiência do índice |
| Cenários fora dos limites | Soluções | Descrição |
|---|---|---|
| Arquivos grandes (imagens/vídeos) | GridFS | Armazenamento em blocos, 255 KB por bloco |
| Texto muito longo | Elasticsearch + Citações | Os documentos armazenam IDs, o ES armazena o texto completo |
| Matriz muito grande (lista de comentários) | Dividir em coleções separadas | coleção de comentários + referência |
| Hierarquia excessivamente profunda | Design plano | Reduzir os níveis da hierarquia |
(1) Limite de 16 MB
(2) Por que 16 MB?
Filosofia de projeto do MongoDB: Evite armazenar documentos muito grandes:
- ✅ Retorna o documento inteiro em uma única consulta (sem JOIN)
- ✅ Alta eficiência na transferência de documentos (adequado para transmissão em rede)
- ❌ Não é adequado para armazenar arquivos binários grandes (use o GridFS)
- ❌ Não é adequado para armazenar textos muito longos (use o Elasticsearch)
(3) Soluções para cenários com documentos de grande porte
graph TB
A[Large-Document Scenarios] --> B[Binary file<br/>Image/Video]
A --> C[Long Text<br/>Article/Log]
A --> D[The array is too large<br/>List of Comments]
B --> E[GridFS<br/>Block Storage]
C --> F[Text Search<br/>Elasticsearch]
D --> G[Split Set<br/>comments Gathering]
style E fill:#d4edda
style F fill:#d4edda
style G fill:#d4edda
▶ Exemplo 5: Armazenamento de arquivos grandes no GridFS
// === Storing Large Files(>16MB)===
const mongoose = require('mongoose');
const Grid = require('gridfs-stream');
const fs = require('fs');
const conn = mongoose.connection;
let gfs;
conn.once('open', () => {
gfs = Grid(conn.db, mongoose.mongo);
gfs.collection('uploads');
});
// Upload File
const writestream = gfs.createWriteStream({
filename: 'large-video.mp4',
content_type: 'video/mp4'
});
fs.createReadStream('./local-video.mp4').pipe(writestream);
writestream.on('close', (file) => {
console.log(`File stored: ${file._id}`);
});
// Download File
const readstream = gfs.createReadStream({
_id: ObjectId('507f1f77bcf86cd799439011')
});
readstream.pipe(fs.createWriteStream('./downloaded-video.mp4'));
8. Documentação incorporada x citações
Explicação do conceito: Existem duas estratégias principais para modelar relações entre documentos no MongoDB — incorporada (Embed) e referenciada (Reference). A abordagem incorporada insere os dados relacionados diretamente no documento pai, permitindo que todos os dados sejam recuperados em uma única consulta; a abordagem referenciada armazena os dados relacionados em coleções separadas, acessadas por meio de referências ObjectId, exigindo múltiplas consultas usando $lookup ou na camada de aplicação. A escolha entre essas duas estratégias é a decisão mais crítica na modelagem de dados do MongoDB.
Como funciona: Os documentos incorporados e os documentos pais são armazenados no mesmo documento BSON e compartilham o mesmo ciclo de vida — quando o documento pai é atualizado, o documento incorporado também é sobrescrito; e quando o documento pai é consultado, o documento incorporado é retornado junto com ele. Documentos referenciados são documentos BSON independentes, com seu próprio _id e ciclo de vida; atualizações em um não afetam o outro, mas consultá-los requer uma operação de associação adicional.
graph TB
A[Document Relationship Modeling] --> B{Data Characteristics}
B -->|1:1 Relationship<br/>Small data set<br/>We often read together| C[Embedded ✅<br/>Retrieve in a single query]
B -->|1:N Relationship<br/>N Smaller<br/>It is rarely checked on its own.| D[Embedded ✅<br/>Nested Arrays]
B -->|1:N Relationship<br/>N Larger<br/>Needs to be checked separately| E[Quotation Style ✅<br/>Independent Set]
B -->|N:N Relationship| F[Quotation Style ✅<br/>Two-way ID Array]
B -->|Frequent Updates to Subdocuments| G[Quotation Style ✅<br/>Avoid rewriting the entire document]
style C fill:#d4edda
style D fill:#d4edda
style E fill:#d4edda
| Cenário | Recomendação | Motivo |
|---|---|---|
| Relação 1:1 (Usuário-Endereço) | Incorporada (a menos que o endereço mude com frequência) | Recuperar tudo em uma única consulta |
| Relação 1:N (Usuário-Pedido) | Depende do valor de N: Pequeno → Incorporado; Grande → Referenciado |
Limite de tamanho do documento |
| Relação N:N (Usuário-Função) | Referência (matriz bidirecional de IDs) | Relação complexa |
| Atualizações frequentes em subdocumentos | Referência | Evite reescrever o documento inteiro |
| Requer consultas separadas para subdocumentos | Referência | Desempenho das consultas separadas |
(1) Duas estratégias para modelar relações
graph TB
subgraph "Embedded Documentation(Embed)"
A1[users Gathering] --> A2[Document 1<br/>address: {<br/> city: Tokyo<br/> country: Japan<br/>}]
end
subgraph "Citation-Style Documentation(Reference)"
B1[users Gathering] --> B2[Document 1<br/>address_id: ObjectId]
B3[addresses Gathering] --> B4[Document 1<br/>city: Tokyo]
B2 -.->|Search| B3
end
(2) Selecione uma estratégia
| Cenário | Recomendação | Motivo |
|---|---|---|
| Relação 1:1 (Usuário-Endereço) | Incorporada (a menos que o endereço mude com frequência) | Recuperar tudo em uma única consulta |
| Relação 1:N (Usuário-Pedido) | Depende do valor de N: Pequeno → Incorporado; Grande → Referenciado |
Limite de tamanho do documento |
| Relação N:N (Usuário-Função) | Referência (matriz bidirecional de IDs) | Relação complexa |
| Atualizações frequentes em subdocumentos | Referência | Evite reescrever o documento inteiro |
| Requer consultas separadas para subdocumentos | Referência | Desempenho das consultas separadas |
(3) Exemplo de um documento incorporado
// === Embedded:User + Multiple Addresses ===
db.users.insertOne({
_id: ObjectId("507f1f77bcf86cd799439011"),
name: "Alice",
email: "alice@example.com",
addresses: [ // Nested Arrays
{
type: "home",
street: "123 Main St",
city: "Tokyo",
country: "Japan",
zip: "100-0001"
},
{
type: "work",
street: "456 Office Rd",
city: "Tokyo",
country: "Japan",
zip: "100-0002"
}
]
});
// === Search:Living in Tokyo users ===
db.users.find({ "addresses.city": "Tokyo" });
▶ Exemplo 6: Exemplo de um documento no estilo de citação
// === Quotation Style:User + Order(Many-to-one) ===
// users Gathering
db.users.insertOne({
_id: ObjectId("507f1f77bcf86cd799439011"),
name: "Alice",
email: "alice@example.com"
});
// orders Gathering
db.orders.insertMany([
{
_id: ObjectId("507f1f77bcf86cd799439012"),
user_id: ObjectId("507f1f77bcf86cd799439011"), // Quote
items: ["PHONE-001", "CASE-002"],
total: NumberDecimal("649.98"),
createdAt: new Date()
},
{
_id: ObjectId("507f1f77bcf86cd799439013"),
user_id: ObjectId("507f1f77bcf86cd799439011"), // Quote
items: ["LAPTOP-001"],
total: NumberDecimal("1299.99"),
createdAt: new Date()
}
]);
// === Usage $lookup Joined Queries(Similar SQL JOIN) ===
db.users.aggregate([
{ $match: { name: "Alice" } },
{ $lookup: {
from: "orders",
localField: "_id",
foreignField: "user_id",
as: "orders"
}}
]);
9. Exercício prático abrangente: Elaboração de documentação do usuário para comércio eletrônico
(1) Requisitos do cenário
Elaborar a documentação do usuário para uma plataforma de comércio eletrônico. Requisitos:
- Informações básicas do usuário (nome, endereço de e-mail, data de cadastro)
- Vários endereços de entrega (integrados)
- Preferências (idioma, moeda, notificações)
- Informações estatísticas (número total de pedidos, vendas totais)
- Lista de seguidores (menciona outros usuários)
- Foto de perfil (referência ao GridFS)
(2) Design do documento
// === Comprehensive User Documentation ===
db.users.insertOne({
_id: ObjectId("507f1f77bcf86cd799439011"),
// === Basic Information ===
email: "alice@example.com",
username: "alice_chen",
displayName: "Alice Chen",
phone: "+81-90-1234-5678",
// === Certification ===
passwordHash: "$2b$10$...", // bcrypt Hash(Not explicitly stated)
emailVerified: true,
twoFactorEnabled: false,
// === Preferences(Nested Documents)===
preferences: {
language: "ja",
currency: "JPY",
timezone: "Asia/Tokyo",
notifications: {
email: true,
sms: false,
push: true,
marketing: false
}
},
// === Shipping Address(Nested Arrays)===
addresses: [
{
addressId: ObjectId("..."),
type: "home",
isDefault: true,
street: "1-2-3 Shibuya",
city: "Tokyo",
prefecture: "Tokyo",
zip: "150-0002",
country: "Japan",
phone: "+81-90-1234-5678"
}
],
// === Statistics(It is recommended to split fields that are updated frequently)===
stats: {
totalOrders: 25,
totalSpent: NumberDecimal("125430.50"),
averageRating: 4.7,
lastOrderAt: ISODate("2026-06-15T10:30:00Z")
},
// === Watchlist(Quotation Style)===
followingIds: [
ObjectId("507f1f77bcf86cd799439012"),
ObjectId("507f1f77bcf86cd799439013")
],
// === Profile Picture Citation(GridFS)===
avatarFileId: ObjectId("507f1f77bcf86cd799439099"),
// === Metadata ===
createdAt: ISODate("2025-03-01T10:00:00Z"),
updatedAt: ISODate("2026-07-01T15:23:00Z"),
lastLoginAt: ISODate("2026-07-01T10:00:00Z"),
isActive: true,
role: "customer" // customer | admin | moderator
});
(3) Mapeamento de esquemas no Mongoose
const UserSchema = new mongoose.Schema({
email: { type: String, required: true, unique: true, lowercase: true },
username: { type: String, required: true, unique: true, index: true },
displayName: { type: String, required: true },
phone: { type: String },
passwordHash: { type: String, required: true, select: false },
emailVerified: { type: Boolean, default: false },
twoFactorEnabled: { type: Boolean, default: false },
preferences: {
language: { type: String, default: 'en' },
currency: { type: String, default: 'USD' },
timezone: { type: String, default: 'UTC' },
notifications: {
email: { type: Boolean, default: true },
sms: { type: Boolean, default: false },
push: { type: Boolean, default: true },
marketing: { type: Boolean, default: false }
}
},
addresses: [{
addressId: { type: mongoose.Schema.Types.ObjectId, default: () => new mongoose.Types.ObjectId() },
type: { type: String, enum: ['home', 'work', 'other'], default: 'home' },
isDefault: { type: Boolean, default: false },
street: { type: String, required: true },
city: { type: String, required: true },
prefecture: String,
zip: { type: String, required: true },
country: { type: String, required: true },
phone: String
}],
stats: {
totalOrders: { type: Number, default: 0 },
totalSpent: { type: mongoose.Schema.Types.Decimal128, default: 0 },
averageRating: { type: Number, default: 0 },
lastOrderAt: Date
},
followingIds: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }],
avatarFileId: { type: mongoose.Schema.Types.ObjectId },
role: { type: String, enum: ['customer', 'admin', 'moderator'], default: 'customer', index: true },
isActive: { type: Boolean, default: true, index: true }
}, { timestamps: true });
❓ Perguntas Frequentes
P: Por que o MongoDB usa BSON em vez de armazenar JSON diretamente? R: O JSON é um formato de texto cuja análise é lenta, possui tipos de dados limitados e não garante a ordem dos campos. O BSON é um formato binário rápido de analisar (em milissegundos), suporta um amplo conjunto de tipos de dados (como Date, Binary e Decimal128) e preserva a ordem dos campos (importante!), tornando-o mais adequado para armazenamento e consultas em bancos de dados.
P: O ObjectId é realmente único? R: Em teoria, o mesmo contador dentro do mesmo processo não será duplicado no mesmo segundo. Na prática, colisões são praticamente impossíveis (um valor aleatório de 5 bytes = 2^40 ≈ 1 trilhão de possibilidades). Se for necessária exclusividade absoluta (por exemplo, no setor financeiro), você pode especificar manualmente
_id: ObjectId()ou um UUID.
P: Os nomes dos campos diferenciam maiúsculas de minúsculas? R: Sim.
firstNameeFirstNamesão campos diferentes. O MongoDB diferencia rigorosamente maiúsculas de minúsculas. Recomendamos o uso de uma convenção de nomenclatura consistente em todo o código (recomenda-se o uso de camelCase).
P: O campo
_idpode ser modificado? R: Sim, mas não é recomendado._idé o identificador exclusivo do documento, e modificá-lo romperá as relações de referência. Se você precisar de uma chave primária de negócios (como um número de pedido), poderá usar uma chave primária personalizada, como_id: "ORDER-2026-07-001", mas isso resultará em um desempenho mais lento nas consultas.
P: Como escolho entre Decimal128 e Double? R: Use Decimal128 para finanças, valores monetários e cálculos precisos. Use Double (que é mais rápido) para computação científica, estatística e renderização gráfica. O uso incorreto do Decimal128 pode levar ao clássico problema
0.1 + 0.2 = 0.30000000000000004.
P: O desempenho das consultas é bom para documentos incorporados? R: Sim. Depois que o MongoDB indexa os documentos incorporados, o desempenho das consultas é comparável ao das coleções independentes. No entanto, observe que: (1) O tamanho total do documento não pode exceder 16 MB; (2) Os índices com várias chaves em campos de matriz têm limitações de tamanho.
P: Os nomes dos campos podem ser em chinês? R: Sim, mas não é recomendado. Por exemplo,
{ name: "Alice" }é válido, mas oferece suporte insuficiente para depuração, registro de logs e ferramentas de terceiros. Recomendamos usar inglês em todos os campos.
📖 Resumo
- O BSON é o formato de armazenamento binário do MongoDB, que suporta mais tipos de dados do que o JSON (como Date, Decimal128 e Binary).
- Um ObjectId é um identificador único de 12 bytes composto por um carimbo de data e hora, um valor aleatório e um contador.
- O BSON suporta mais de 12 tipos de dados, com ênfase na distinção entre Double, Int32, Long e Decimal128
- Recomendamos usar camelCase para os nomes dos campos; evite nomes que comecem com
$ou que não contenham.. - O limite de tamanho para um único documento é de 16 MB; use o GridFS para arquivos grandes e o Elasticsearch para textos longos.
- As relações incorporadas são adequadas para relações 1:1 e para um pequeno número de relações 1:N, enquanto as relações referenciais são adequadas para relações complexas
- O Mongoose Schema define as estruturas dos documentos e lida automaticamente com as conversões de tipos BSON
📝 Exercícios
-
Pergunta básica (⭐): Insira um documento de produto no Mongosh (contendo 6 ou mais campos: String, Número, Data, Matriz, Objeto, Booleano) e, em seguida, use
findOne()para consultar e verificar os tipos dos campos. -
Exercício Básico (⭐): Escreva um script em Node.js para criar um esquema de usuário usando o Mongoose (incluindo um campo
balancedo tipoDecimal128e um campoavatardo tipoBuffer), inserir dados e exibir o carimbo de data/hora doObjectId. -
Exercício avançado (⭐⭐): Elabore uma estrutura de documento para uma postagem de blog (com 5 ou mais campos), use
insertManypara inserir 5 postagens e demonstre o design de uma matriz de comentários incorporada. -
Problema avançado (⭐⭐): Escreva um script para extrair os carimbos de data e hora dos campos ObjectId de 100 documentos, agrupá-los por data e contar o número de documentos para cada dia.
-
Problema avançado (⭐⭐): Compare o desempenho das consultas em documentos incorporados e referenciados: utilizando 1 milhão de registros, armazene a relação “usuário-pedido” tanto pelo método de armazenamento incorporado quanto pelo referenciado e avalie os tempos de resposta por meio de
$lookupe consultas aninhadas. -
Desafio (⭐⭐⭐): Use o GridFS para implementar uma API de upload/download de arquivos que suporte o envio de arquivos de até 100 MB, verifique o mecanismo de armazenamento em blocos e implemente o acompanhamento do progresso do download.