MongoDB: Padrões avançados do Mongoose
Última atualização: 2026-10-09
Padrões avançados do Mongoose — Domine recursos avançados como populate, discriminator, lean e aggregate.
1. O que você vai aprender
- Preencher consultas de junção (em comparação com $lookup)
- discriminador
- Otimização de desempenho da função
lean() - Pipeline de agregação Model.aggregate()
- Declaração de índice de esquema
- Dicas de otimização de desempenho para o Mongoose 7.x
graph TB
A[mongoose Advanced Features] --> B[populate<br/>Application Layer JOIN]
A --> C[discriminator<br/>Single Set, Multiple Schema]
A --> D[lean<br/>Performance Optimization]
A --> E[aggregate<br/>Database-Level Aggregation]
A --> F[Schema Index<br/>Declarative]
B --> B1[Multiple queries<br/>N+1 Risks]
C --> C1[role Field<br/>Classification by Type]
D --> D1[Skip hydrate<br/>↑5x Performance]
E --> E1[$facet/$lookup<br/>Return Multiple Dimensions in a Single Operation]
style D fill:#d4edda
2. Preencher consultas com junções
O que é populate? populate é a implementação do Mongoose de um “JOIN na camada de aplicação” — quando um esquema contém um campo ref do tipo ObjectId, o populate emite automaticamente uma consulta adicional para substituir o ObjectId referenciado pelo documento completo. Essencialmente, isso envolve duas consultas: primeiro, recuperar o ObjectId do documento primário e, em seguida, recuperar os dados completos do documento associado.
Os princípios fundamentais do populate:
sequenceDiagram
participant App as Node.js Applications
participant Mongo as MongoDB
App->>Mongo: 1st query: Order.find()
Mongo-->>App: [order1, order2, ...]
App->>Mongo: 2nd query: User.find({_id: {$in: [ObjectId1, ObjectId2, ...]}})
Mongo-->>App: Back users
App->>App: Merge: order.userId → user Object
Comparação entre populate e $lookup:
| Dimensão | preencher | $lookup |
|---|---|---|
| Camada de execução | Camada de aplicação (2 ou mais consultas) | Camada de banco de dados (1 agregação) |
| Número de consultas | Risco N+1 | 1 |
| Flexibilidade | Média (suporta apenas referências a ObjectId) | Alta (associações condicionais arbitrárias) |
| Desempenho | Adequado para conjuntos de dados pequenos | Recomendado para conjuntos de dados grandes |
| Concisão do código | Alta (uma linha de .populate()) | Baixa (sintaxe do pipeline de agregação) |
| Tipo de retorno | Documento Mongoose | objeto simples |
O Problema N+1: Ao consultar 100 pedidos e usar populate em cada order.userId, isso gera 1 (consulta de pedidos) + 100 (consultas de usuários) = 101 consultas. O Mongoose otimiza isso automaticamente para uma consulta em lote $in (1+1=2 consultas), mas operações populate aninhadas ainda podem gerar consultas adicionais.
Casos de uso: Para relações um-para-um (por exemplo, recuperar o usuário associado a um pedido), use populate; para relações em massa com condições complexas, use $lookup; para fins exclusivamente de exibição, use lean combinado com populate.
(1) Preenchimento básico
// === Basic populate ===
const user = await User.findById(userId).populate('addresses');
// SELECT u.*, a.* FROM users u LEFT JOIN addresses a ON u._id = a.userId
// === Nested populate ===
const order = await Order.findById(orderId)
.populate('userId')
.populate({
path: 'items.productId',
select: 'sku title price'
});
// === Conditions populate ===
const orders = await Order.find()
.populate({
path: 'userId',
match: { isActive: true },
select: 'username avatar'
});
| populate vs $lookup | populate | $lookup |
|---|---|---|
| Camada de execução | Camada de aplicação | Camada de banco de dados |
| Número de consultas | N+1 | 1 |
| Flexibilidade | Média | Alta |
| Desempenho | Adequado para conjuntos de dados pequenos | Recomendado para conjuntos de dados grandes |
3. Discriminador
O que é um discriminador? Um discriminador é o mecanismo de “herança de coleção única” do Mongoose — vários modelos compartilham a mesma coleção do MongoDB, com os tipos de documentos diferenciados por uma chave discriminadora. Isso é semelhante à herança na programação orientada a objetos: a classe base define campos comuns, as subclasses a estendem com campos específicos e todas as instâncias residem na mesma tabela.
Mecanismo subjacente do discriminador:
graph TB
subgraph "users Gathering(Single Set)"
D1["{role: 'Customer', loyaltyPoints: 100, email: 'alice@...'}"]
D2["{role: 'Customer', loyaltyPoints: 50, email: 'bob@...'}"]
D3["{role: 'Admin', permissions: ['manage'], email: 'admin@...'}"]
end
subgraph "mongoose Model Layer"
User[User Model<br/>email + username + passwordHash]
Customer[Customer Model<br/>+ loyaltyPoints + preferredCategories]
Admin[Admin Model<br/>+ permissions + lastLoginAt]
end
User -->|discriminator| Customer
User -->|discriminator| Admin
Customer -->|Search: role='Customer'| D1
Customer -->|Search: role='Customer'| D2
Admin -->|Search: role='Admin'| D3
style User fill:#fff3cd
style Customer fill:#d4edda
style Admin fill:#cce5ff
Discriminador x Conjunto Independente:
| Dimensão | Discriminador (conjunto único) | Conjunto independente |
|---|---|---|
| Método de consulta | Filtro automático por chave de identificação | Consulta entre conjuntos |
| Eficiência de armazenamento | Alta (índices compartilhados) | Baixa (índices duplicados em campos comuns) |
| Consistência dos dados | Naturalmente consistente (dentro do mesmo conjunto) | Requer manutenção (atualizações entre conjuntos) |
| Tamanho do índice | Pequeno (um índice para campos comuns) | Grande (criar um índice separado para cada coleção) |
| Desempenho da consulta | Um pouco mais lento (requer filtragem por função) | Rápido (conjunto menor) |
| Escalabilidade | Ruim (expansão de conjuntos) | Boa (escalonamento independente) |
| Casos de uso | Diferenças mínimas entre campos, consultas com junções frequentes | Diferenças significativas entre campos, consultas principalmente independentes |
Casos de uso: Funções de usuário (Cliente/Administrador/Moderador compartilham e-mail e senha, cada uma com seus próprios campos específicos); formas de pagamento (Cartão de crédito/PayPal/Transferência bancária compartilham valor e status, cada uma com campos específicos do canal); tipos de notificação (E-mail/SMS/Notificação push compartilham assunto e conteúdo, cada um com configurações específicas do canal).
// === Basics User Model ===
const UserSchema = new mongoose.Schema({
email: String,
username: String,
passwordHash: String
}, { discriminatorKey: 'role' });
const User = mongoose.model('User', UserSchema);
// === Customer Discriminator ===
const Customer = User.discriminator('Customer', new mongoose.Schema({
loyaltyPoints: { type: Number, default: 0 },
preferredCategories: [String]
}));
// === Admin Discriminator ===
const Admin = User.discriminator('Admin', new mongoose.Schema({
permissions: [String],
lastLoginAt: Date
}));
// === Create Different Roles ===
const customer = await Customer.create({
email: 'alice@example.com',
username: 'alice',
passwordHash: '...',
loyaltyPoints: 100,
preferredCategories: ['Electronics']
});
const admin = await Admin.create({
email: 'admin@example.com',
username: 'admin',
passwordHash: '...',
permissions: ['manage_products']
});
// === When querying, based on role Category ===
const customers = await Customer.find();
const admins = await Admin.find();
// All data is in the same collection(users), Through discriminatorKey Category
Caso de uso: Coleção única com vários esquemas (campos diferentes para funções diferentes).
4. Otimização de desempenho da função lean()
O que é lean()? lean() é um método de otimização de desempenho no Mongoose — ele ignora o processo de hidratação do documento e retorna diretamente um objeto JavaScript simples. Enquanto as consultas comuns retornam um documento Mongoose (com métodos como save() e validate(), além do rastreamento de alterações), lean() retorna um objeto simples (contendo apenas dados, sem nenhum método).
Princípios por trás das diferenças de desempenho no lean():
graph LR
subgraph "General Query (without lean)"
T1[Product.find] --> D1[mongoose Document<br/>with save/validate etc.<br/>~150ms / 100 docs]
end
subgraph "lean Search"
Q2[MongoDB Original BSON] --> H2[JSON.parse Direct Conversion]
H2 --> D2[plain object<br/>Raw Data<br/>~30ms / 100 docs]
end
style D1 fill:#f8d7da
style D2 fill:#d4edda
Dados de comparação de desempenho (100 documentos, categoria Eletrônicos):
| Operação | Sem Lean | Com Lean | Multiplicador de desempenho |
|---|---|---|---|
| Tempo de consulta | ~150 ms | ~30 ms | ↑5x |
| Uso de memória | ~5 MB | ~1 MB | ↓5x |
| JSON.stringify | ~8 ms | ~2 ms | ↑4x |
| Suporta save() | ✅ | ❌ | — |
| Suporta preenchimento | ✅ | ✅ (chamadas encadeadas) | — |
| Compatível com Controle de Alterações | ✅ | ❌ | — |
Diretrizes de uso: Use lean() para APIs somente leitura (listas, detalhes); não use lean() se precisar chamar save() ou modificar o rastreamento; adicione lean() se não forem necessárias outras modificações após populate.
// === General Inquiry:Back mongoose Document ===
const products = await Product.find();
// Each product is a Mongoose Document (with save() and other methods)
// === lean():Return to Pure JS Object ===
const products = await Product.find().lean();
// Each product is a plain object, Performance up 3-5x
// === Comparison Test ===
console.time('without lean');
const a = await Product.find({ category: 'Electronics' }).limit(100);
console.timeEnd('without lean'); // ~150ms
console.time('with lean');
const b = await Product.find({ category: 'Electronics' }).lean().limit(100);
console.timeEnd('with lean'); // ~30ms
5. Pipeline de agregação Model.aggregate()
Canais de agregação no Mongoose: Model.aggregate() aciona diretamente o mecanismo de agregação do MongoDB para realizar agrupamentos, junções e cálculos na camada do banco de dados — ao contrário do populate, que lida com essas operações na camada de aplicação; os dados nos canais de agregação não precisam ser transferidos para o lado do Node.js para processamento, resultando em melhor desempenho.
Guia para escolher entre aggregate e populate:
| Cenário | Solução recomendada | Motivo |
|---|---|---|
| Consultar pedido + nome de usuário | preencher | Relação simples, código limpo |
| Calcular o preço médio para cada categoria | agregar | Os cálculos devem ser agrupados |
| Juntar + Agrupar + Ordenar | agregar + $lookup | Tudo em uma única etapa |
| Junções aninhadas em vários níveis | agregação + várias chamadas $lookup | Evitar N+1 |
| Retornar resultados multidimensionais | aggregate + $facet | Retornar várias visualizações de uma só vez |
Processo de execução do pipeline de agregação:
graph LR
Input[1,000,000 Documents] -->|"$match"| Filter[After filtering: 500,000]
Filter -->|"$group"| Group[Group by category<br/>5 groups]
Group -->|"$sort"| Sorted[Sort by count]
Sorted -->|"$limit"| Output[Top 5]
style Input fill:#f8d7da
style Output fill:#d4edda
// === mongoose Using Aggregation in ===
const stats = await Product.aggregate([
{ $match: { isActive: true } },
{ $group: { _id: '$category', count: { $sum: 1 }, avgPrice: { $avg: '$price' } } },
{ $sort: { count: -1 } }
]);
// === aggregate + populate(mongoose 6+)===
const results = await Order.aggregate([
{ $match: { status: 'paid' } },
{
$lookup: {
from: 'users',
localField: 'userId',
foreignField: '_id',
as: 'customer'
}
},
{ $unwind: '$customer' }
]);
6. Declaração de índice de esquema
Métodos de declaração de índices no Mongoose: O Mongoose oferece suporte à criação declarativa de índices dentro das definições de esquema — índices no nível do campo (index: true), índices compostos (Schema.index()) e índices especiais (índices de texto, índices TTL e índices parciais). A vantagem dos índices declarativos é que eles são definidos junto com o esquema, tornando-os fáceis de entender à primeira vista; eles são criados automaticamente na inicialização (autoIndex=true).
Tipos de índices e casos de uso:
| Tipo de índice | Método de declaração | Casos de uso | Parâmetros especiais |
|---|---|---|---|
| Índice de campo único | { sku: { index: true } } |
Consultas de igualdade, ordenação | único |
| Índice Composto | Schema.index({a:1, b:-1}) |
Consulta com múltiplas condições | Regra ESR |
| Índice de texto | { title: { text: true } } |
Pesquisa de texto completo | Pesos |
| Índice TTL | Schema.index({at:1}, {expireAfterSeconds:86400}) |
Expiração automática | Tempo de expiração |
| Índice parcial | partialFilterExpression |
Índice de condições | Filtrar condições |
| Índice geográfico | { loc: { type: '2dsphere' } } |
Pesquisa geográfica | — |
Regra ESR (Equality-Sort-Range): A ordem dos campos em um índice composto deve seguir a seguinte sequência: condições de igualdade → condições de classificação → condições de intervalo. Por exemplo, { category: 1, price: -1 } suporta find({category:'E'}) + sort({price:-1}), mas não suporta a classificação apenas por preço.
const ProductSchema = new mongoose.Schema({
sku: { type: String, index: true, unique: true },
title: { type: String, text: true }, // Text Index
price: { type: Number, index: true },
category: { type: String, index: true }
});
// === Composite Index ===
ProductSchema.index({ category: 1, price: -1 });
// === Selected Indexes ===
ProductSchema.index(
{ category: 1 },
{ partialFilterExpression: { isActive: true } }
);
// === TTL Index ===
ProductSchema.index(
{ createdAt: 1 },
{ expireAfterSeconds: 30 * 24 * 60 * 60 }
);
7. Otimização de desempenho do Mongoose 7.x
Metodologia de otimização de desempenho do Mongoose: A otimização de desempenho não se resume simplesmente a “adicionar uma chamada lean() e dar o trabalho por encerrado”; envolve, na verdade, um ajuste sistemático desde a camada de conexão → camada de consulta → camada de aplicação → camada de implantação. Os princípios fundamentais são: reduzir o volume de transferência de dados, minimizar o número de consultas, reduzir a sobrecarga de serialização e aproveitar os recursos nativos do banco de dados.
Matriz de Estratégias de Otimização:
| Camada de otimização | Estratégia | Eficácia | Invasividade |
|---|---|---|---|
| Camada de conexão | Ajuste do maxPoolSize | Reduzir o tempo de espera pela conexão | Baixo (Configuração) |
| Camada de consulta | Seleção de projeção (select()) | Reduz a transferência de dados em mais de 90% | Baixa |
| Nível da consulta | Índice + hint() | Evitar varredura completa da tabela | Médio |
| Camada de consulta | lean() | Reduz a sobrecarga do Hydrate em 5 vezes | Baixa |
| Camada de aplicação | Paralelismo com Promise.all | Reduz o tempo de espera em série | Baixo |
| Camada de aplicação | Operações em lote com bulkWrite | Reduz as idas e voltas na rede em 10 vezes ↑ | Média |
| Camada de Aplicação | Transmissão de cursor | Como evitar o estouro de memória | Médio |
| Nível de implantação | autoIndex=false | Inicialização mais rápida | Baixo |
| Camada de implantação | Separação de leitura e gravação | Reduz a carga no nó primário | Alta |
Práticas de otimização de Charlie: A API da lista de produtos da TechCorp foi otimizada, passando de 3 segundos para 50 milissegundos — ① Adicionou-se índices compostos para evitar o COLLSCAN; ② Utilizou-se select() para consultar apenas 5 campos; ③ Utilizou-se lean() para pular hydrate; ④ Utilizou Promise.all para executar find e count em paralelo; ⑤ Definia um limite de 100 linhas.
// === Optimization 1:Disable autoIndex(Production)===
mongoose.connect(uri, { autoIndex: false });
// Manually create indexes at startup:await Product.syncIndexes();
// === Optimization 2:Bulk Operations ===
await Product.bulkWrite([
{ updateOne: { filter: { sku: 'A' }, update: { $inc: { stock: -1 } } } },
{ updateOne: { filter: { sku: 'B' }, update: { $inc: { stock: -1 } } } }
]);
// === Optimization 3:Projection Reduces Data Transmission ===
const products = await Product.find()
.select('sku title price') // Search only 3 field
.lean();
// === Optimization 4:Usage cursor Streaming Processing of Big Data ===
const cursor = Product.find().cursor();
for await (const doc of cursor) {
// Process each document
}
// === Optimization 5:Bulk Insert ===
await Product.insertMany(docs, { ordered: false });
8. Treinamento prático abrangente
// === Optimized List API ===
app.get('/api/products', async (req, res) => {
const { page = 1, limit = 20, category, search } = req.query;
// 1. Build a Query
const query = { isActive: true };
if (category) query.category = category;
if (search) query.$text = { $search: search };
// 2. Parallel Queries(find + count)
const [products, total] = await Promise.all([
Product.find(query)
.select('sku title price thumbnail rating') // Projection
.sort({ createdAt: -1 })
.limit(+limit)
.skip((page - 1) * limit)
.lean(), // Performance Optimization
Product.countDocuments(query)
]);
res.json({
success: true,
data: products,
meta: { page: +page, limit: +limit, total, pages: Math.ceil(total / limit) }
});
});
▶ Exemplo 1: Preenchimento de relações multiníveis + otimização enxuta do desempenho
// === Scene:ShopHub Order Details API(3 Layer Association)===
const mongoose = require('mongoose');
// Schemas
const AddressSchema = new mongoose.Schema({ city: String, country: String, zipCode: String });
const UserSchema = new mongoose.Schema({
email: String, username: String,
addresses: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Address' }]
});
const ProductSchema = new mongoose.Schema({ sku: String, title: String, price: Number, thumbnail: String });
const OrderSchema = new mongoose.Schema({
orderNumber: String,
userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
items: [{ productId: { type: mongoose.Schema.Types.ObjectId, ref: 'Product' }, qty: Number, price: Number }],
status: String
}, { timestamps: true });
const Address = mongoose.model('Address', AddressSchema);
const User = mongoose.model('User', UserSchema);
const Product = mongoose.model('Product', ProductSchema);
const Order = mongoose.model('Order', OrderSchema);
// 3-level populate + lean
const order = await Order.findById('647f1f77bcf86cd799439001')
.populate({ path: 'userId', select: 'username email',
populate: { path: 'addresses', select: 'city country' }
})
.populate({ path: 'items.productId', select: 'sku title price' })
.lean();
console.log({
orderNumber: order.orderNumber,
customer: order.userId.username,
city: order.userId.addresses[0]?.city,
items: order.items.map(i => `${i.productId.title} x${i.qty}`)
});
// Output:{ orderNumber: 'ORD-001', customer: 'alice', city: 'San Francisco',
// items: ['Smartphone X x2', 'Laptop Pro x1'] }
Saída: populate de 3 níveis (Order→User→Address + Order→Product) + otimização de desempenho com lean().
Resultado: 3 camadas de
populate(Pedido → Usuário → Endereço + Pedido → Produto) + otimização de desempenholean().
▶ Exemplo: E-commerce Dashboard com populate, lean, e aggregate (Difficulty ⭐⭐)
// Scene: ShopHub admin dashboard showing orders with customer and product details
const mongoose = require('mongoose');
// Schema with refs
const OrderSchema = new mongoose.Schema({
orderNumber: String,
customerId: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
items: [{
productId: { type: mongoose.Schema.Types.ObjectId, ref: 'Product' },
qty: Number,
price: Number
}],
status: { type: String, enum: ['pending', 'paid', 'shipped', 'delivered'] },
total: Number,
createdAt: { type: Date, default: Date.now }
});
const Order = mongoose.model('Order', OrderSchema);
// Method 1: populate (good for small datasets)
async function getOrdersWithPopulate() {
return await Order.find({ status: 'paid' })
.populate('customerId', 'username email')
.populate('items.productId', 'sku title')
.sort({ createdAt: -1 })
.limit(20)
.lean(); // Combine with lean for read-only
}
// Method 2: aggregate with $lookup (better for large dataset + filtering)
async function getOrdersWithAggregate() {
return await Order.aggregate([
{ $match: { status: 'paid' } },
{ $sort: { createdAt: -1 } },
{ $limit: 20 },
// Join customer
{
$lookup: {
from: 'users',
localField: 'customerId',
foreignField: '_id',
as: 'customer'
}
},
{ $unwind: '$customer' },
// Join products
{
$lookup: {
from: 'products',
localField: 'items.productId',
foreignField: '_id',
as: 'productDetails'
}
},
// Final shape
{
$project: {
orderNumber: 1,
total: 1,
status: 1,
customer: { username: '$customer.username', email: '$customer.email' },
itemCount: { $size: '$items' }
}
}
]);
}
// Usage
const orders = await getOrdersWithPopulate();
console.log(`${orders.length} orders loaded with customer info`);
Saída:
TEXT20 orders loaded with customer info // Each order includes customer.username, customer.email, and items with product details
▶ Exemplo 2: Uma aplicação prática abrangente de Populate + Discriminator + Lean
// === 1. populate Multi-level relationships ===
// Order + User + Products(3 Nested Layers)
const order = await Order.findById(orderId)
.populate({
path: 'userId',
select: 'username email avatar',
populate: { path: 'addresses', select: 'city country' } // Address under the user
})
.populate({
path: 'items.productId',
select: 'sku title price thumbnail'
})
.lean(); // Performance Optimization
console.log('Order:', {
orderNumber: order.orderNumber,
customer: {
username: order.userId.username,
address: order.userId.addresses[0]?.city
},
items: order.items.map(i => ({
product: i.productId.title,
qty: i.qty,
price: i.price
}))
});
// === 2. discriminator Single Set, Multiple Schema ===
// User Base Class
const UserSchema = new mongoose.Schema({
email: { type: String, required: true, unique: true },
username: String,
passwordHash: String,
createdAt: { type: Date, default: Date.now }
}, { discriminatorKey: 'role' });
const User = mongoose.model('User', UserSchema);
// Customer Discriminator(Inheritance User + Extended Fields)
const Customer = User.discriminator('Customer', new mongoose.Schema({
loyaltyPoints: { type: Number, default: 0 },
preferredCategories: [String],
totalSpent: mongoose.Schema.Types.Decimal128
}));
// Admin Discriminator
const Admin = User.discriminator('Admin', new mongoose.Schema({
permissions: [String],
lastLoginAt: Date
}));
// Create Different Roles(All exist users Gathering, Through role Field Delimiter)
const customer = await Customer.create({
email: 'alice@example.com',
username: 'alice',
passwordHash: '...',
loyaltyPoints: 100,
preferredCategories: ['Electronics']
});
const admin = await Admin.create({
email: 'admin@example.com',
username: 'admin',
passwordHash: '...',
permissions: ['manage_products', 'manage_users']
});
// When querying, based on role Automatic Filtering
const customers = await Customer.find({ loyaltyPoints: { $gt: 50 } });
// Actual Query:{ role: 'Customer', loyaltyPoints: { $gt: 50 } }
// === 3. lean() Performance Optimization Comparison ===
console.time('without lean');
const a = await Product.find({ category: 'Electronics' }).limit(100);
console.timeEnd('without lean'); // ~150ms
console.time('with lean');
const b = await Product.find({ category: 'Electronics' }).lean().limit(100);
console.timeEnd('with lean'); // ~30ms(5x Performance Improvements)
// === 4. Model.aggregate() Database-Level Aggregation ===
const stats = await Product.aggregate([
{ $match: { isActive: true } },
{
$facet: {
totalCount: [{ $count: 'count' }],
byCategory: [
{ $group: { _id: '$category', count: { $sum: 1 }, avgPrice: { $avg: '$price' } } },
{ $sort: { count: -1 } }
],
topRated: [
{ $sort: { rating: -1 } },
{ $limit: 5 },
{ $project: { sku: 1, title: 1, rating: 1 } }
]
}
}
]);
// Output:
// {
// totalCount: [{ count: 1250 }],
// byCategory: [
// { _id: 'Electronics', count: 450, avgPrice: 599 },
// { _id: 'Books', count: 380, avgPrice: 29 },
// ...
// ],
// topRated: [
// { sku: 'PHONE-001', title: 'Smartphone X', rating: 4.9 },
// ...
// ]
// }
Saída: populate realiza associações em vários níveis; discriminator permite vários papéis em uma única coleção; lean() melhora o desempenho em 5x; aggregate agrega na camada do banco e retorna resultados multidimensionais de uma só vez.
Resultado:
populateimplementa associações em vários níveis;discriminatorsuporta múltiplas funções dentro de um único conjunto;lean()proporciona um aumento de 5 vezes no desempenho; eaggregaterealiza uma única agregação na camada do banco de dados para retornar resultados multidimensionais.
❓ Perguntas Frequentes
populate em vez de $lookup?populate para conjuntos de dados pequenos (flexibilidade na camada de aplicação) e $lookup para conjuntos de dados grandes (eficiência na camada de banco de dados).save() depois de lean()?lean() retorna um objeto simples que não possui métodos do Mongoose. Se você precisar chamar save(), consulte o documento novamente ou use os métodos do documento.📖 Resumo
- preencher: Consultas com junções (risco N+1)
- discriminador: conjunto único, múltiplos esquemas
- lean(): Otimização de desempenho ↑3–5x
- Model.aggregate(): Agregação no nível do banco de dados
- Declaração de índice do esquema
- Dicas de otimização de desempenho para o Mongoose 7.x
📝 Exercícios
- Problema básico (⭐): Implemente uma consulta de junção
populate(Pedido + Usuário + Produto). - Problema básico (⭐): Use
lean()para otimizar a API da lista de produtos e comparar as diferenças de desempenho. - Problema avançado (⭐⭐): Use um discriminador para implementar as três funções: Usuário, Cliente e Administrador.
- Exercício avançado (⭐⭐): Use
bulkWritepara atualizar o estoque de produtos em massa (lidar com situações de falta de estoque). - Desafio (3 estrelas): Conclua as APIs avançadas do Mongoose (populate + lean + aggregate + discriminator).