MongoDB Map/Reduce数组聚合问题

8

我有一个MongoDB集合,其中文档使用多层嵌套,我想从中提取一组由它们的子集字段编译而成的多维数组。我现在有一个可行的解决方案,但我想更好地理解“幂等性”这个概念及其与reduce函数相关的后果。

{
  "host_name" : "gateway",
  "service_description" : "PING",
  "last_update" : 1305777787,
  "performance_object" : [
    [ "rta", 0.105, "ms", 100, 500, 0 ],
    [ "pl", 0, "%", 20, 60, 0 ]
  ]
}

以下是map/reduce函数:

var M = function() {
  var hn = this.host_name, 
      sv = this.service_description, 
      ts = this.last_update;
  this.performance_object.forEach(function(P){
    emit( { 
      host: hn, 
      service: sv, 
      metric: P[0] 
    }, { 
      time: ts, 
      value: P[1] 
    } );
  });
}
var R = function(key,values) {
  var result = { 
    time: [], 
    value: [] 
  };
  values.forEach(function(V){
    result.time.push(V.time);
    result.value.push(V.value);
  });
  return result;
}
db.runCommand({
  mapreduce: <colname>,
  out: <col2name>,
  map: M,
  reduce: R
});

数据以有用的结构返回,我使用finalize重新格式化/排序以进行图形化。

{
  "_id" : {
    "host" : "localhost",
    "service" : "Disk Space",
    "metric" : "/var/bck"
  },
  "value" : {
    "time" : [
      [ 1306719302, 1306719601, 1306719903, ... ],
      [ 1306736404, 1306736703, 1306737002, ... ],
      [ 1306766401, 1306766701, 1306767001, ... ]
    ],
    "value" : [
      [ 122, 23423, 25654, ... ],
      [ 336114, 342511, 349067, ... ],
      [ 551196, 551196, 551196, ... ]
    ]
  }
}

最后...

 [ [1306719302,122], [1306719601,23423], [1306719903,25654], ... ]

TL;DR:观察到“分段”数组结果的预期行为是什么?

我了解到,reduce函数可能会在发射值的数组上调用多次,这就是为什么有几个“块”组成完整的数组,而不是一个单独的数组。数组块通常包含25-50个项目,很容易在finalize()中清理它们。我使用concat()连接数组,交替将它们作为[time,value]排序。但我真正想知道的是,是否可以变得更加复杂:

1)观察到的分块是由于我的代码、MongoDB的实现还是Map/Reduce算法本身引起的?

2)在分片配置或仅因我的草率实现而导致更深层次(递归)嵌套的数组块,是否会破坏concat()方法?

3)获取如上所示的数组结果的更好策略是否存在?

编辑:修改为发射数组:

我采纳了Thomas的建议并将其重写为发射数组。拆分值绝对没有任何意义。

var M = function() {
  var hn = this.host_name, 
      sv = this.service_description, 
      ts = this.last_update;
  this.performance_object.forEach(function(P){
    emit( { 
      host: hn, 
      service: sv, 
      metric: P[0] 
    }, { 
      value: [ ts, P[1] ] 
    } );
  });
}
var R = function(key,values) {
  var result = {
    value: [] 
  };
  values.forEach(function(V){
    result.value.push(V.value);
  });
  return result;
}
db.runCommand({
  mapreduce: <colname>,
  out: <col2name>,
  map: M,
  reduce: R
});

现在的输出结果类似于这样:
{
  "_id" : {
    "host" : "localhost",
    "service" : "Disk Space",
    "metric" : "/var/bck"
  },
  "value" : {
    "value" : [
      [ [1306736404,336114],[1306736703,342511],[1306737002,349067], ... ],
      [ [1306766401,551196],[1306766701,551196],[1306767001,551196], ... ],
      [ [1306719302,122],[1306719601,122],[1306719903,122], ... ]
    ]
  }
}

我使用了这个finalize函数来连接数组块并对它们进行排序。

...
var F = function(key,values) {
  return (Array.concat.apply([],values.value)).sort(function(a,b){ 
    if (a[0] < b[0]) return -1;
    if (a[0] > b[0]) return 1;
    return 0;
  });
}
db.runCommand({
  mapreduce: <colname>,
  out: <col2name>,
  map: M,
  reduce: R,
  finalize: F
});

这个方案很好:

{
  "_id" : {
    "host" : "localhost",
    "service" : "Disk Space",
    "metric" : "/mnt/bck"
  },
  "value" : [ [1306719302,122],[1306719601,122],[1306719903,122],, ... ]
}

我想唯一困扰我的问题是,这个Array.concat.apply([],values.value)是否能始终信任以清理reduce的输出。

上次编辑:更简单了...

自从上面给出的原始示例以来,我修改了文档结构,但这只是通过使映射函数变得非常简单来改变示例。

我仍在努力摆脱为什么Array.prototype.push.apply(result,V.data)的工作方式与result.push(V.data)如此不同...但它有效。

var M = function() {
  emit( { 
    host: this.host, 
    service: this.service, 
    metric: this.metric
  } , { 
    data: [ [ this.timestamp, this.data ] ] 
  } );
}
var R = function(key,values) {
  var result = [];
  values.forEach(function(V){
    Array.prototype.push.apply(result, V.data);
  });
  return { data: result };
}
var F = function(key,values) {
  return values.data.sort(function(a,b){
    return (a[0]<b[0]) ? -1 : (a[0]>b[0]) ? 1 : 0;
  });
}

它的输出与上面“LAST EDIT”标题下方所示的相同。
谢谢,Thomas!
1个回答

3
  1. “chunking”是由您的代码产生的:您的reduce函数的value参数可以包含来自map函数发射的{time:<timestamp>,value:<value>},或者来自先前调用reduce函数返回的{time:[<timestamps>],value:[<values]}

  2. 我不知道在实践中是否会发生,但从理论上讲它是可能的。

  3. 只需让map函数发射与reduce函数返回的相同类型的对象,即emit(<id>, {time: [ts], value: [P[1]]}),并相应地更改reduce函数,即Array.push.apply(result.time, V.time),对于result.value也是如此。

    我其实不明白为什么您不使用时间/值对的数组,而是使用一对数组,即在map函数中使用emit(<id>, { pairs: [ {time: ts, value: P[1] ] })emit(<id>, { pairs: [ [ts, P[1]] ] }),在reduce函数中使用Array.push.apply(result.pairs, V.pairs)。这样,您甚至不需要finalize函数(除了可能需要将数组从“pairs”属性中“解封”:因为reduce函数无法返回数组,所以必须以对象的方式包装它)。


谢谢您的回复,发射数组是最优的。我已经更新了主题,并且现在我只想弄清楚reduce是否会抛出任何更深层嵌套的数组。 - jcampbelly
你犯了和之前一模一样的错误:你在 map 函数中发出的内容与在 reduce 函数中返回的内容不同:一个包含“pair”,而另一个则有一组“pairs”的数组。坚持使用完全相同的“schema”,你就不会遇到任何问题,也不会有嵌套的数组。 - Thomas Broyer
我一有机会就会尝试这个:emit( { host: hn, service: sv, metric: P[0] }, { value: [ [ ts, P[1] ] ] } ); - jcampbelly

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接