如何在长时间运行的mapreduce操作中避免游标超时?

时间:2013-06-11 10:39:40

标签: mongodb mapreduce

我正在针对分片群集上的mongos实例对大型集合运行重复检测mapreduce操作,我希望该操作花费的时间超过10分钟:

m = function () {
    emit(this.fieldForDupCheck, 1);
}
r = function (k, vals) {
    return Array.sum(vals);
}
res = db.Collection.mapReduce(m, r, { out : "dups" });

运行此操作会在处理约10分钟后出现以下错误:

uncaught exception: map reduce failed:{
"ok" : 0,
"errmsg" : "MR post processing failed: { result: "dups", errmsg: "exception: getMore: cursor didn't exist on server, possible restart or timeout?", code: 13127, ok: 0.0 }"
}

我在mapReduce调用上使用.addOption(DBQuery.Option.noTimeout)尝试了adding a noTimeout option,但这会导致shell Object [object Object] has no method 'addOption'中的JS错误

如何在长时间运行的mapreduce操作中避免游标超时?

1 个答案:

答案 0 :(得分:6)

您还没有提到您正在使用的MongoDB版本,但解决方案与此处提供的内容类似。我将在2.2.4之上进行演示,这是Ubuntu 13.04附带的内容。

执行此操作的问题确实是将选项注入游标。这就是addOption生活的地方:

> var cursor = db.test.find()
> cursor.addOption
function (option) {
    this._options |= option;
    return this;
}

让我们看看如何定义mapReduce

> db.test.mapReduce
function (map, reduce, optionsOrOutString) {
    var c = {mapreduce:this._shortName, map:map, reduce:reduce};
    ...
    var raw = this._db.runCommand(c);
    ...
    return new MapReduceResult(this._db, raw);
}

因此它构建了一个文档以通过runCommand运行命令。让我们进一步研究它:

> db.runCommand
function (obj) {
    if (typeof obj == "string") {
        var n = {};
        n[obj] = 1;
        obj = n;
    }
    return this.getCollection("$cmd").findOne(obj);
}

因此命令通过findOne运行。我们来看看它:

> db.test.findOne
function (query, fields, options) {
    var cursor = this._mongo.find(this._fullName, this._massageObject(query) || {}, fields, -1, 0, 0, options || this.getQueryOptions());
    if (!cursor.hasNext()) {
        return null;
    }
    var ret = cursor.next();
    ...
    return ret;
}

啊,这里有一些有趣的东西。正在使用来自参数options的标记来初始化游标,遗憾的是这不会对您的情况有所帮助,因为runCommand使其未设置,但它与getQueryOptions()进行OR运算,来自采集。我们来看看它:

> db.collection.getQueryOptions
function () {
    var options = 0;
    if (this.getSlaveOk()) {
        options |= 4;
    }
    return options;
}

哎呀..这不好。因此,我们无法访问游标,也无法通过非hackish手段将查询选项注入执行的命令。

好吧,但我们已经学到了很多关于map reduce命令实际上是如何通过该过程传递给服务器的。它只是一个针对数据库中特定集合查询的文档。这意味着我们可以构建相同的查询,并自己运行它,但提供任何必要的标志。

我不会解决构建整个MongoDB命令并为您设置结果的麻烦,但我只是通过使用isMaster命令向您展示它实际上是有效的。

这是没有任何标志的命令:

> db.getCollection("$cmd").findOne({isMaster: 1}).ismaster
true

要查看效果上的差异,我们将tcpdump与数据库进行通信。我们可以在the wire protocol documentation中看到相关标志位于集合名称之前,为32位整数,因此很容易发现转储的相关部分:

    .                  vvvvvvvvv
    0x0040:  d407 0000 0000 0000 7465 7374 2e24 636d  ........test.$cm
    0x0050:  6400 0000 0000 ffff ffff 1700 0000 0169  d..............i

好。我们可以在集合名称之前看到四个字节归零。

现在,让我们在提供一些标志的同时做同样的事情。我们从上面的调试部分了解到,查询标志可以作为findOne的第三个选项提供,所以让我们这样做:

> db.getCollection("$cmd").findOne({isMaster: 1}, undefined, 0xBEEF).ismaster
true

并查看转储:

    .                  vvvvvvvvv
    0x0040:  d407 0000 efbe 0000 7465 7374 2e24 636d  ........test.$cm
    0x0050:  6400 0000 0000 ffff ffff 1700 0000 0169  d..............i

嘿,我们的旗帜在他们应该的地方交付,我们也可以看到它被反转,这意味着字节被编码为小端,匹配the docs

因此,这意味着您可以提供标记DBQuery.Option.noTimeout作为findOne的第三个选项,并按照与in the documentation所述类似的方式对map-reduce命令进行手动编码我们用isMaster做了,你会得到你想要的东西。