Cha*_*yra 20 cursor mongodb node.js mongodb-query
我需要sid在大约500K文档的集合中为每个文档创建一个新字段.每个sid都是独一无二的,并基于该记录的现有roundedDate和stream字段.
我正在使用以下代码:
var cursor = db.getCollection('snapshots').find();
var iterated = 0;
var updated = 0;
while (cursor.hasNext()) {
var doc = cursor.next();
if (doc.stream && doc.roundedDate && !doc.sid) {
db.getCollection('snapshots').update({ "_id": doc['_id'] }, {
$set: {
sid: doc.stream.valueOf() + '-' + doc.roundedDate,
}
});
updated++;
}
iterated++;
};
print('total ' + cursor.count() + ' iterated through ' + iterated + ' updated ' + updated);
Run Code Online (Sandbox Code Playgroud)
它起初效果很好,但几个小时后大约有100K记录,它出错了:
Error: getMore command failed: {
"ok" : 0,
"errmsg": "Cursor not found, cursor id: ###",
"code": 43,
}: ...
Run Code Online (Sandbox Code Playgroud)
Dan*_*ger 67
正如@NeilLunn在他的评论中指出的那样,你不应该手动过滤文档,而是使用.find(...)它来代替:
db.snapshots.find({
roundedDate: { $exists: true },
stream: { $exists: true },
sid: { $exists: false }
})
Run Code Online (Sandbox Code Playgroud)
此外,使用.bulkWrite()(从中MongoDB 3.2获得)将比单独更新更具性能.
有可能,您可以在光标的10分钟生命周期内执行查询.如果它仍然需要更多,你的光标将过期,无论如何你将遇到相同的问题,这将在下面解释:
Error: getMore command failed 可能是由于游标超时,这与两个游标属性有关:
超时限制,默认为10分钟.来自文档:
默认情况下,服务器将在10分钟不活动后自动关闭光标,或者客户端已用尽光标.
批量大小,第一批为101个文档或16 MB,后续批次为16 MB,无论文档数是多少(从MongoDB开始3.4).来自文档:
find()aggregate()默认情况下,操作的初始批处理大小为101个文档.针对生成的游标发出的后续getMore操作没有默认的批处理大小,因此它们仅受16兆字节消息大小的限制.
您可能正在使用这些最初的101个文档,然后获得16 MB的批处理,这是最大的,并且有更多的文档.由于处理它们需要10多分钟,服务器上的光标会超时,当您完成第二批处理文档并请求新文档时,光标已经关闭:
当您遍历游标并到达返回批处理的末尾时,如果有更多结果,cursor.next()将执行getMore操作以检索下一批.
我看到5种可能的方法来解决这个问题,3个好的方法,有利有弊,2个不好的方法:
减小批量大小以保持光标处于活动状态.
从光标中删除超时.
光标到期时重试.
手动查询批量结果.
在光标到期之前获取所有文档.
请注意,它们没有按照任何特定标准编号.仔细阅读并确定哪一种最适合您的特定情况.
解决这个问题的一种方法是使用cursor.bacthSize在find查询返回的光标上设置批量大小,以匹配那些在10分钟内可以处理的批量大小:
const cursor = db.collection.find()
.batchSize(NUMBER_OF_DOCUMENTS_IN_BATCH);
Run Code Online (Sandbox Code Playgroud)
但是,请记住,设置非常保守(小)的批处理大小可能会起作用,但也会更慢,因为现在您需要访问服务器更多次.
另一方面,将其设置为太接近于您可以在10分钟内处理的文档数量的值意味着如果某些迭代由于任何原因需要更长时间处理(其他进程可能消耗更多资源) ,光标无论如何都会过期,你会再次遇到同样的错误.
另一种选择是使用cursor.noCursorTimeout来防止游标超时:
const cursor = db.collection.find().noCursorTimeout();
Run Code Online (Sandbox Code Playgroud)
这被认为是一种不好的做法,因为您需要手动关闭光标或耗尽其所有结果,以便自动关闭:
设置
noCursorTimeout选项后,您必须手动关闭光标,cursor.close()或者通过耗尽光标的结果.
由于您希望处理游标中的所有文档,因此您无需手动关闭它,但仍可能在代码中出现其他错误并在完成之前抛出错误,从而使游标保持打开状态.
如果您仍想使用此方法,请在使用try-catch其所有文档之前使用a 确保在出现任何错误时关闭光标.
注意我不认为这是一个糟糕的解决方案(因此),因为即使认为它被认为是一种不好的做法......:
这是驱动程序支持的功能.如果它是如此糟糕,因为有其他方法可以解决超时问题,如其他解决方案中所述,这将不受支持.
有一些方法可以安全地使用它,这只是一个特别谨慎的问题.
我假设您没有定期运行此类查询,因此您开始在任何地方开始使用开放游标的可能性很低.如果不是这种情况,并且你真的需要一直处理这些情况,那么不使用就没有意义noCursorTimeout.
基本上,你把你的代码放在一个try-catch,当你得到错误时,你得到一个新的光标跳过你已经处理的文件:
let processed = 0;
let updated = 0;
while(true) {
const cursor = db.snapshots.find().sort({ _id: 1 }).skip(processed);
try {
while (cursor.hasNext()) {
const doc = cursor.next();
++processed;
if (doc.stream && doc.roundedDate && !doc.sid) {
db.snapshots.update({
_id: doc._id
}, { $set: {
sid: `${ doc.stream.valueOf() }-${ doc.roundedDate }`
}});
++updated;
}
}
break; // Done processing all, exit outer loop
} catch (err) {
if (err.code !== 43) {
// Something else than a timeout went wrong. Abort loop.
throw err;
}
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,您需要对此解决方案的结果进行排序才能生效.
使用此方法,您可以使用最大可能的批处理大小16 MB来最小化对服务器的请求数,而无需预测在10分钟内您将能够处理多少文档.因此,它也比以前的方法更健壮.
基本上,您使用skip(),limit()和sort()来执行多个查询,这些查询包含您认为可以在10分钟内处理的大量文档.
我认为这是一个糟糕的解决方案,因为驱动程序已经可以选择设置批量大小,因此没有理由手动执行此操作,只需使用解决方案1并且不要重新发明轮子.
此外,值得一提的是,它与解决方案1有相同的缺点,
由于结果处理,您的代码可能需要一些时间才能执行,因此您可以先检索所有文档然后再处理它们:
const results = new Array(db.snapshots.find());
Run Code Online (Sandbox Code Playgroud)
这将一个接一个地检索所有批次并关闭光标.然后,您可以遍历内部的所有文档results并执行您需要执行的操作.
但是,如果您遇到超时问题,可能是您的结果集非常大,因此将内存中的所有内容拉出可能不是最明智的做法.
如果由于文档大小的增加而干预写入操作会移动它们,则可能会多次返回某些文档.要解决这个问题,请使用cursor.snapshot().来自文档:
将snapshot()方法附加到游标以切换"快照"模式.这确保了查询不会多次返回文档,即使由于文档大小的增加而介入的写入操作导致文档移动也是如此.
但是,请记住它的局限性:
请注意解决方案5,移动可能导致重复文档检索的文档的时间窗口比其他解决方案要窄,因此您可能不需要snapshot().
在您的特定情况下,当调用集合时snapshot,可能不会更改,因此您可能不需要snapshot().此外,您正在基于其数据对文档进行更新,并且一旦更新完成,即使多次检索同一文档也不会再次更新,因为if条件将跳过它.
要查看打开游标的使用次数db.serverStatus().metrics.cursor.
| 归档时间: |
|
| 查看次数: |
18655 次 |
| 最近记录: |