MongoDB-错误:getMore命令失败:未找到光标 [英] MongoDB - Error: getMore command failed: Cursor not found

查看:7
本文介绍了MongoDB-错误:getMore命令失败:未找到光标的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要在大约500K文档集合中的每个文档上创建一个新字段sid。每个sid都是唯一的,并且基于该记录的现有roundedDatestream字段。

我使用以下代码执行此操作:

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);

起初工作正常,但几个小时后,大约100K记录了错误:

Error: getMore command failed: {
    "ok" : 0,
    "errmsg": "Cursor not found, cursor id: ###",
    "code": 43,
}: ...

推荐答案

编辑-查询性能:

正如@NeilLunn在他的评论中指出的那样,您不应该手动过滤文档,而应该使用.find(...)

db.snapshots.find({
    roundedDate: { $exists: true },
    stream: { $exists: true },
    sid: { $exists: false }
})

此外,使用MongoDB 3.2中提供的.bulkWrite()将比单独更新性能高得多。

有了它,您就有可能在游标的10分钟生存期内执行查询。如果仍然需要更多时间,您的游标将到期,并且无论如何都会遇到相同的问题,下面将对此进行解释:

这里发生了什么:

Error: getMore command failed可能是游标超时,这与两个游标属性有关:

  • 超时限制,默认为10分钟。From the docs

    默认情况下,服务器将在10分钟不活动后自动关闭游标,或者如果客户端耗尽了游标。

  • 批大小,第一批为101个文档或16MB,后续批为16MB,不管有多少个文档(从MongoDB3.4开始)。From the docs

    find()aggregate()操作的初始批处理大小默认为101个文档。针对结果游标发出的后续getMore操作没有默认批处理大小,因此它们仅受16 MB消息大小的限制。

您可能正在使用最初的101个文档,然后获得16MB的批处理(这是最大值),其中包含更多的文档。由于处理它们的时间超过10分钟,服务器上的光标超时,当您处理完第二批文档and request a new one时,光标已经关闭:

当您遍历游标并到达返回的批处理的末尾时,如果有更多的结果,cursor.next()将执行一个getMore操作来检索下一个批处理。


可能的解决方案:

我认为解决这个问题有5种可能的方法,3种好的方法各有优缺点,2种不好的方法:

  1. 👍减小批处理大小以保持光标活动。

  2. 👍从游标中删除超时。

  3. 游标过期时👍重试。

  4. 👎手动分批查询结果。

  5. 👎在游标过期之前获取所有文档。

请注意,它们的编号不遵循任何特定标准。通读一遍并决定哪一种最适合您的特定情况。


1.👍减小批处理大小以保持光标活动

解决此问题的一种方法是使用cursor.bacthSizefind查询返回的游标上设置批处理大小,以匹配您可以在这10分钟内处理的批处理大小:

const cursor = db.collection.find()
    .batchSize(NUMBER_OF_DOCUMENTS_IN_BATCH);

但是,请记住,设置非常保守的(小)批处理大小可能会奏效,但也会较慢,因为现在您需要访问服务器的次数更多。

另一方面,将其设置为与您在10分钟内可以处理的文档数太接近的值意味着,如果某些迭代由于任何原因(其他进程可能正在消耗更多资源)花费更长的时间来处理,则光标无论如何都会过期,并且您将再次收到相同的错误。


2.👍从光标移除超时

另一个选项是使用cursor.noCursorTimeout防止游标超时:

const cursor = db.collection.find().noCursorTimeout();

这被认为是一种不好的做法,因为您需要手动关闭光标或用尽其所有结果以使其自动关闭:

设置noCursorTimeout选项后,必须使用cursor.close()手动关闭游标,或用尽游标的结果。

因为您要处理游标中的所有文档,所以不需要手动关闭它,但仍有可能代码中出现其他错误,并在您完成之前引发错误,从而使游标保持打开状态。

如果您仍要使用此方法,请使用try-catch确保在使用其所有文档之前,如果出现任何错误,请关闭游标。

注意:我不认为这是一个糟糕的解决方案(因此是👍),因为即使它被认为是一个糟糕的做法...:

  • 这是驱动程序支持的功能。如果情况如此糟糕,因为有其他方法可以解决超时问题(如其他解决方案中所述),则不支持此方法。

  • 安全使用它的方法是有的,只是要格外小心。

  • 我假设您不会定期运行这种查询,因此您开始将打开的游标放在各处的可能性很低。如果不是这样,并且您确实需要一直处理这些情况,则不使用noCursorTimeout是有意义的。


3.游标过期时👍重试

基本上,您将代码放在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;
        }
    }
}

注意:您需要对结果进行排序,此解决方案才能正常工作。

使用这种方法,您可以使用最大可能的批处理大小16MB来最小化对服务器的请求数量,而不必事先猜测您将能够在10分钟内处理多少文档。因此,它也比以前的方法更健壮。


4.👎手动批量查询结果

基本上,您可以使用skip()limit()sort()对您认为可以在10分钟内处理的多个文档执行多个查询。

我认为这是一个糟糕的解决方案,因为驱动程序已经有了设置批处理大小的选项,所以没有理由手动执行此操作,只需使用解决方案1,不要重新发明轮子。

另外,值得一提的是,它与解决方案1具有相同的缺点


5.👎获取光标过期前的所有文档

由于结果处理,您的代码可能需要一些时间才能执行,因此您可以先检索所有文档,然后再处理它们:

const results = new Array(db.snapshots.find());

这将逐个检索所有批次并关闭光标。然后,您可以遍历results中的所有文档并执行您需要执行的操作。

但是,如果您遇到超时问题,则可能是您的结果集非常大,因此调出内存中的所有内容可能不是最明智的做法。


有关快照模式和复制文档的说明

如果由于文档大小的增长而导致中间写操作移动了某些文档,则可能会多次返回这些文档。要解决此问题,请使用cursor.snapshot()From the docs

将SNAPSHOT()方法附加到游标以切换"SNAPSHOT"模式。这可确保查询不会多次返回文档,即使中间的写入操作会因文档大小增加而导致文档移动。

但是,请记住它的局限性:

  • 它不适用于分片的集合。

    /li>
  • 它不适用于sort()hint(),因此它不适用于解决方案3和4。

  • 它不保证隔离插入或删除。

注意:使用解决方案5时,移动可能导致重复文档检索的文档的时间窗口比使用其他解决方案时要短,因此您可能不需要snapshot()

在您的特定情况下,因为集合称为snapshot,所以它可能不太可能更改,因此您可能不需要snapshot()。此外,您正在基于文档的数据对文档进行更新,一旦完成更新,即使多次检索同一文档,也不会再次更新该文档,因为if条件将跳过该文档。


有关打开的游标的说明

若要查看打开的游标的计数,请使用db.serverStatus().metrics.cursor

这篇关于MongoDB-错误:getMore命令失败:未找到光标的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆