我正在针对分片群集上的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操作中避免游标超时?
答案 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
做了,你会得到你想要的东西。