高并发场景下Mongoose写入MongoDB异常问题求助
高并发场景下Mongoose插入性能异常排查与解决思路
问题场景
- 压测架构:
mock.js通过socket.io每秒向server.js推送10K条数据,server.js异步调用db.js,通过Mongoose向MongoDB执行单条插入 - 预期表现:Mongoose创建数百个MongoDB连接,每秒完成约9K次插入
- 异常表现:
- 压测期间MongoDB仅产生数十次插入、维持约10个连接,无明显响应
- 停止压测后,MongoDB插入量和连接数瞬间飙升,直至完成所有积压操作
- 持续压测会导致
server.js先OOM,之后MongoDB才开始处理请求
核心原因分析
- Mongoose连接池默认限制
Mongoose默认连接池大小(maxPoolSize)仅为10,高并发下单条插入请求会被阻塞在连接池队列中,无法及时发送到MongoDB,导致请求在server.js内存中大量积压,最终引发OOM。 - 单条插入的低效性
每秒10K条单条插入会产生大量网络往返和事务开销,即使连接池足够大,单条插入的吞吐量也远低于批量插入,无法匹配压测的请求速率。 - 请求积压的连锁反应
压测期间请求持续涌入,但MongoDB处理能力跟不上,所有未处理的请求会在server.js内存中排队,内存占用持续升高直至OOM;停止压测后队列中的请求才会被批量发送到MongoDB,导致插入量和连接数瞬间飙升。
针对性解决方法
1. 调整Mongoose连接池配置
显式增大连接池大小,同时设置合理超时参数,避免请求长时间积压:
// db.js 中的连接配置 mongoose.connect('mongodb://localhost:27017/yourDB', { maxPoolSize: 150, // 根据服务器CPU核心数和MongoDB承载能力调整,建议100-200 socketTimeoutMS: 30000, connectTimeoutMS: 30000 });
2. 改用批量插入替代单条插入
将请求攒成批量后提交,大幅减少网络往返次数,这是提升插入吞吐量最有效的方式:
// db.js 中实现批量插入逻辑 let batchBuffer = []; const BATCH_SIZE = 1000; // 每攒够1000条提交一次,可根据实际调整 async function insertData(data) { batchBuffer.push(data); if (batchBuffer.length >= BATCH_SIZE) { const currentBatch = [...batchBuffer]; batchBuffer = []; try { await YourModel.insertMany(currentBatch, { ordered: false }); // ordered: false 允许部分失败不中断整体 } catch (err) { console.error('批量插入失败:', err); // 可额外记录失败的数据,后续重试 } } } // 程序退出时提交剩余缓冲数据 process.on('exit', async () => { if (batchBuffer.length > 0) { await YourModel.insertMany(batchBuffer, { ordered: false }); } });
3. 控制请求流入速率
在server.js中对socket.io请求做限流,避免接收超出处理能力的请求,从源头防止内存积压:
// server.js 中实现socket限流 const socketIoLimiter = require('socket.io-rate-limiter'); const limiter = socketIoLimiter({ max: 10000, // 每秒最多处理10K条,根据实际处理能力调整 duration: 1000 }); io.on('connection', (socket) => { limiter(socket); socket.on('data', async (data) => { await insertData(data); }); });
4. 排查事件循环阻塞
用node --trace-event-categories v8,node.async_hooks启动服务,排查是否存在同步计算、IO阻塞等操作,确保异步插入请求能及时进入事件循环处理。
内容的提问来源于stack exchange,提问作者OlderFox
相关产品推荐
相关产品推荐

