我在运行针对MongoDB实例的node.js服务器应用程序的集成测试时出现类似死锁的行为。我们的测试代码和服务器代码都使用Mongoose和node.js驱动程序来访问MongoDB。
测试框架删除数据库,并在每个测试套件的开头(或有时是单个测试)为每个集合重新创建索引。这种情况在测试过程中会多次发生。
类似死锁的行为似乎发生在对db.collection.ensureIndex
的一系列调用期间。一旦发生这种情况,所有执行都将停止,直到测试超时到期,这将中止测试。如果我在没有超时的情况下运行测试,那么挂起似乎是永远的。
在这一点上,我觉得自己很挣扎,并希望听到关于调试方法或寻找线索的方向的建议。非常感谢。
根据MongoDB Concurrency FAQ,ensureIndex
在没有background:true
选项的情况下调用时需要独占数据库写锁('W')。但是,我已通过Mongoose调试日志验证,在这些调用期间使用了background:true
,例如:
mails.ensureIndex({ expires: 1, mailType: 1, readOn: 1 }) { background: true }
但是,MongoDB日志(至少对我来说)显示尝试了的独占锁
:command: createIndexes { createIndexes: "mails", indexes: [ { name: "expires_1_mailType_1_readOn_1", key: { expires: 1, mailType: 1, readOn: 1 }, background: true } ], writeConcern: { w: 1 } } numYields:0 reslen:113 locks:{ Global: { acquireCount: { r: 1, w: 1 } }, Database: { acquireCount: { w: 1, W: 2 }, acquireWaitCount: { w: 1, W: 2 }, timeAcquiringMicros: { w: 1027663, W: 193312 } }, Collection: { acquireCount: { w: 1 } } } protocol:op_query 1289ms
同时,当系统处于此状态时,我无法使用任何客户端连接到数据库,包括mongo CLI,mongotop,mongostat或node.js驱动程序。因此,我无法运行db.currentOp()
或db.serverStatus()
来收集有关数据库状态的信息。一旦我终止测试,客户端就可以连接并且数据库恢复正常状态。我不知道这是否是由全局独占锁引起的,但确定似乎全局。
当node.js客户端代码在docker容器(运行Linux)中执行时,此问题会100%出现。但是,在Windows上针对完全相同的MongoDB实例运行相同的客户端代码时,它永远不会发生。
这让我想知道问题可能出在MongoDB的node.js驱动程序的Linux版本上。我在某种程度上对此进行了研究,但还没有找到任何结论。我正在设置容器外的另一个Linux环境,看看我是否可以重现那里的行为。
(编辑)我无法在我的Linux机器上重新解决问题,直接通过shell运行,或者在docker容器中运行。所以,我已经更新了这个问题的标题以反映这一点。此外,我已经在另一台Windows 10 Pro计算机上遇到了问题。
删除数据库
mongoose.connection.db.dropDatabase(callback);
重建索引
// (inside a larger async.waterfall block)
// Loop through all schemas and recreate all indexes.
_.each(mongoose.connection.base.modelSchemas, function (schema, key) {
asyncFunctions.push(function (cb) {
mongoose.model(key, schema).ensureIndexes(err => {
return cb(err);
});
});
});
async.parallel(asyncFunctions, function (err) {
return callback(err);
});
我并不是说这是一个真正的数据库死锁。我还不确定。它就像一个僵局。
我也尝试过使用 * mongoose@4.9.0(mongodb@2.2.24) * mongod --version => v3.4.2 这增加了驱动程序级别的超时,但是当我将它们设置为高时,类似死锁的行为仍然存在。
我尝试用每个集合调用collect.remove()来替换调用以删除数据库,但这花了很长时间才导致在该操作阶段超时。
我也尝试用每个集合的collection.drop()替换数据库drop,但这会将死锁移动到此操作阶段(而不是ensureIndex)。
最后,我尝试使用async.series()替换async.parallel()调用ensureIndexes(上面),但这只是延迟了死锁。