Batch operations and efficient writing
Concept and Advantages of Batch Operations
In Mongoose, batch operations refer to performing CRUD operations on multiple documents simultaneously. Compared to single operations, batch operations can significantly reduce database round trips and improve overall performance. Typical scenarios include initializing large amounts of data, batch updating statuses, and data migration. For example, an e-commerce system needing to adjust prices for 1,000 products would find a batch update more efficient than executing update
1,000 times in a loop.
// Inefficient single updates
for (const product of products) {
await Product.updateOne(
{ _id: product._id },
{ $set: { price: product.newPrice } }
);
}
// Efficient batch update
const bulkOps = products.map(product => ({
updateOne: {
filter: { _id: product._id },
update: { $set: { price: product.newPrice } }
}
}));
await Product.bulkWrite(bulkOps);
Detailed Explanation of Model.bulkWrite() Method
bulkWrite()
is Mongoose's most powerful batch operation method, supporting mixed operation types. Its core parameter is an array of operations, where each element defines a specific operation:
await Character.bulkWrite([
{
insertOne: {
document: { name: "Eddard Stark", house: "Stark" }
}
},
{
updateMany: {
filter: { house: "Lannister" },
update: { $set: { isAlive: false } }
}
},
{
deleteOne: {
filter: { name: "Viserys Targaryen" }
}
}
]);
Operation types include:
insertOne
updateOne
/updateMany
deleteOne
/deleteMany
replaceOne
Optimization Strategies for Batch Insertion
When inserting data in bulk, insertMany()
can be over 10 times faster than looping create()
. The key parameter ordered
controls whether operations execute sequentially (default: true). Setting it to false
can further improve speed but requires handling potential partial failures:
// Unordered insertion (faster)
await User.insertMany(
userList,
{ ordered: false }
);
// Handling partial failure errors
try {
await User.insertMany(badData, { ordered: false });
} catch (err) {
console.log(err.writeErrors); // View specific failed records
console.log(err.insertedIds); // View successful record IDs
}
Benchmark comparison (10,000 records):
- Single insertion: ~12 seconds
- Ordered batch insertion: ~1.8 seconds
- Unordered batch insertion: ~0.9 seconds
Special Syntax for Batch Updates
Mongoose provides various batch update syntactic sugar, with updateMany()
being the most straightforward:
// Mark all expired coupons as invalid
await Coupon.updateMany(
{ expireDate: { $lt: new Date() } },
{ $set: { isValid: false } }
);
Complex updates can use aggregation pipelines:
await Order.updateMany(
{ status: "shipped" },
[
{ $set: {
lastUpdated: "$$NOW",
trackingInfo: { $concat: ["SH-", "$orderId"] }
}}
]
);
Write Concern and Performance Trade-offs
The write concern level of batch operations affects performance and reliability:
// Sacrifice some reliability for performance
await Log.bulkWrite(ops, {
w: 1, // Only primary node confirmation required
j: false, // Do not wait for journal write
wtimeout: 5000 // Timeout after 5 seconds
});
// High-reliability configuration
await Payment.bulkWrite(ops, {
w: "majority", // Majority node confirmation required
j: true // Wait for journal persistence
});
Error Handling Patterns
Batch operations require special error handling strategies:
try {
const result = await Product.bulkWrite([
{ insertOne: { document: { /*...*/ } }},
{ updateMany: { /*...*/ }}
]);
console.log(result); // Contains nInserted, nModified, etc.
} catch (err) {
if (err.name === 'BulkWriteError') {
console.error('Partial operation failure:', err.result);
console.error('Failure details:', err.writeErrors);
} else {
throw err;
}
}
Common error types:
BulkWriteError
: Partial operation failureValidationError
: Document validation failureCastError
: Type conversion failure
Combining with Transactions
In MongoDB 4.0+, batch operations can be combined with transactions:
const session = await mongoose.startSession();
try {
session.startTransaction();
await Order.bulkWrite([...], { session });
await Inventory.bulkWrite([...], { session });
await session.commitTransaction();
} catch (err) {
await session.abortTransaction();
throw err;
} finally {
session.endSession();
}
Practical Application Scenarios
Scenario 1: User Data Migration
const oldUsers = await OldUserModel.find().lean();
const ops = oldUsers.map(user => ({
insertOne: {
document: {
username: user.login,
email: user.contact.email,
migratedAt: new Date()
}
}
}));
const result = await NewUserModel.bulkWrite(ops, { ordered: false });
console.log(`Migration complete, ${result.nInserted} records succeeded`);
Scenario 2: Log Batch Compression
// Compress DEBUG logs older than 1 hour into a summary
await Log.bulkWrite([
{
insertOne: {
document: {
type: "SUMMARY",
message: "DEBUG logs summary",
count: { $sum: 1 },
level: "INFO"
}
}
},
{
deleteMany: {
filter: {
level: "DEBUG",
createdAt: { $lt: new Date(Date.now() - 3600000) }
}
}
}
]);
Performance Monitoring and Tuning
Analyze batch operations via explain()
:
const explain = await Model.bulkWrite(ops).explain();
console.log(explain.executionStats);
Key metrics:
executionTimeMillis
: Total execution timenReturned
: Number of documents returnedkeysExamined
: Number of index checks
Optimization recommendations:
- Create indexes for frequently queried fields
- Control batch operation size to 1,000-5,000 documents per batch
- Avoid mixing read and write operations in batch operations
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:删除文档(Delete)
下一篇:条件查询与复杂过滤