MongoDB+Node.js高效批量插入百万用户通知记录最佳实践
MongoDB + Node.js 批量生成100万条用户通知最佳实践
针对100万存量用户批量生成对应通知的需求,优先选择MongoDB原生聚合管道写入方案,不需要手动遍历用户做逐次插入,全程在数据库层完成运算和写入,性能最高;如果通知内容需要经过Node.js层做复杂逻辑处理,再用游标分批读取+批量插入的方案即可。
方案一:数据库原生聚合写入(性能最优,优先推荐)
MongoDB 4.2及以上版本支持聚合管道$merge阶段,可以直接从源集合读取数据,加工成目标结构后直接写入目标集合,全程不需要把数据传输到应用层,没有网络IO开销,100万条数据通常几秒到十几秒就能跑完,是这类批量生成场景的最优解。
实现代码(Node.js驱动)
const { MongoClient } = require('mongodb'); async function batchCreateNotifications() { const client = new MongoClient('你的MongoDB连接地址'); await client.connect(); const db = client.db('你的业务库名'); const usersCollection = db.collection('Users'); await usersCollection.aggregate([ // 把用户文档转换成通知结构 { $project: { userID: "$_id", // 关联用户ID data: { $literal: "test data" }, // 固定通知内容,可替换为聚合支持的动态计算逻辑 isSeen: { $literal: false }, _id: 0 // 必须设为0,让MongoDB为通知自动生成新的主键ObjectId,避免主键冲突 } }, // 把生成的通知文档写入Notifications集合 { $merge: { into: "Notifications", on: "_id", whenMatched: "keepExisting", // 重复执行任务时不覆盖已存在的通知 whenNotMatched: "insert" } } ]).toArray(); // 触发聚合任务执行 await client.close(); } batchCreateNotifications();
注意事项
- 执行前建议在业务低峰期操作,避免占用过多数据库IO影响线上业务
- 任务执行完成后,可以给
Notifications集合的userID字段建普通索引,后续查询用户通知效率更高
方案二:Node.js层分批处理(适合需要自定义复杂逻辑的场景)
如果通知内容需要调用外部服务、或者有复杂的业务逻辑必须在Node.js层计算生成,不要全量拉取所有用户到内存,也不要单条循环插入,采用游标分批读取+批量插入的模式实现,平衡内存占用和写入性能。
实现代码(Node.js驱动)
const { MongoClient } = require('mongodb'); async function batchCreateNotificationsWithLogic() { const client = new MongoClient('你的MongoDB连接地址'); await client.connect(); const db = client.db('你的业务库名'); const usersCollection = db.collection('Users'); const notifCollection = db.collection('Notifications'); const BATCH_SIZE = 2000; // 单批处理条数,建议1000-5000之间,根据服务和DB性能调整 let pendingBatch = []; // 用游标逐批拉取用户,不会全量加载到内存 const userCursor = usersCollection.find({}, { projection: { _id: 1 } }).batchSize(BATCH_SIZE); for await (const user of userCursor) { // 此处可写自定义业务逻辑,生成对应通知内容 const notification = { userID: user._id, data: "test data", isSeen: false }; pendingBatch.push(notification); // 凑够一批就批量写入 if (pendingBatch.length >= BATCH_SIZE) { await notifCollection.insertMany(pendingBatch, { ordered: false // 无序写入,跳过错误继续执行,写入性能更高 }); pendingBatch = []; } } // 写入最后不足一批的剩余数据 if (pendingBatch.length > 0) { await notifCollection.insertMany(pendingBatch, { ordered: false }); } await client.close(); } batchCreateNotificationsWithLogic();
避坑提醒
- 禁止全量拉取用户:不要写
const allUsers = await usersCol.find({}).toArray()这类代码,100万条BSON文档加载到内存至少占数百MB,很容易触发Node.js进程OOM崩溃 - 禁止单条循环插入:逐次调用
insertOne会产生大量网络往返开销,写入效率比批量插入低两个量级,100万条数据可能跑数小时都无法完成 - 单批大小不要超过5000:单批数据量过大会触发MongoDB单请求16MB大小限制,还会造成数据库瞬时IO压力过高
- 副本集环境可临时调整写入配置:批量写入时可以临时把writeConcern设为
w:1,不需要等待从节点确认写入,速度可提升30%以上,任务完成后改回原有配置即可
内容的提问来源于stack exchange,提问作者Mahmoud
相关产品推荐
相关产品推荐

