批量插入文档上的mongo / mongoid MapReduce

时间:2014-06-11 19:35:06

标签: javascript ruby mongodb mapreduce mongoid

我创建我的批处理并使用下面指定的命令

将其插入集合
batch = []
time = 1.day.ago

(1..2000).each{ |i| a = {:name => 'invbatch2k'+i.to_s, :user_id =>  BSON::ObjectId.from_string('533956cd4d616323cf000000'), :out_id => 'out', :created_at => time, :updated_at => time, :random => '0.5' }; batch.push a; }

Invitation.collection.insert batch

如上所述,每个邀请记录都将user_id字段值设置为'533956cd4d616323cf000000'

created_at: 1.day.ago插入我的批次后

我得到:

2.1.1 :102 > Invitation.lte(created_at: 1.week.ago).count
 => 48
2.1.1 :103 > Invitation.lte(created_at: Date.today).count
 => 2048

也:

2.1.1 :104 > Invitation.lte(created_at: 1.week.ago).where(user_id: '533956cd4d616323cf000000').count
 => 14
2.1.1 :105 > Invitation.where(user_id: '533956cd4d616323cf000000').count
 => 2014

另外,我有一个地图缩小功能,用于计算每个唯一用户发送的邀请(总数和发送到唯一的out_id)

class Invitation

  [...]

  def self.get_user_invites_count
    map = %q{
      function() {
        var user_id = this.user_id;
        emit(user_id, {user_id : this.user_id, out_id: this.out_id, count: 1, countUnique: 1})
      }
    }
    reduce = %q{
      function(key, values) {
        var result = {
          user_id: key,
          count: 0,
          countUnique : 0
        };
        var values_arr = [];
        values.forEach(function(value) {
          values_arr.push(value.out_id);
          result.count += 1
        });
        var unique = values_arr.filter(function(item, i, ar){ return ar.indexOf(item) === i; });
        result.countUnique = unique.length;
        return result;
      }
    }
    map_reduce(map,reduce).out(inline: true).to_a.map{|d| d['value']} rescue []
  end
end

问题是:

Invitation.lte(created_at: Date.today.end_of_day).get_user_invites_count

返回

[{"user_id"=>BSON::ObjectId('533956cd4d616323cf000000'), "count"=>49.0, "countUnique"=>2.0} ...]

而不是"count" => 2014, "countUnique" => 6.0 while:

Invitation.lte(created_at: 1.week.ago).get_user_invites_count返回:

[{"user_id"=>BSON::ObjectId('533956cd4d616323cf000000'), "count"=>14.0, "countUnique"=>6.0} ...]

查询提供的数据在插入批次之前是准确的。

我无法绕过这里发生的事情。我错过了什么吗?

1 个答案:

答案 0 :(得分:1)

您似乎在documentation中遗漏的部分似乎是问题所在:

  

MongoDB可以为同一个密钥多次调用reduce函数。在这种情况下,该键的reduce函数的先前输出将成为该键的下一个reduce函数调用的输入值之一。

还有以后:

  

返回对象的类型必须与map函数发出的值的类型相同,以确保以下操作为真:

所以你看到你的reduce函数返回的签名与它从mapper接收的输入不同。这很重要,因为reducer可能无法在单次传递中获得给定键的全部值。相反,它会获得其中的一部分,“减少”结果,减少的输出可以与键的其他值(可能还会减少)组合,再进一步通过reduce函数。

由于您的字段不匹配,后续的减少传递不会看到这些值,也不会计入您的总数。因此,您需要对齐值的签名:

  def self.get_user_invites_count
    map = %q{
      function() {
        var user_id = this.user_id;
        emit(user_id, {out_id: this.out_id, count: 1, countUnique: 0})
      }
    }
    reduce = %q{
      function(key, values) {
        var result = {
          out_id: null,
          count: 0,
          countUnique : 0
        };
        var values_arr = [];
        values.forEach(function(value) {
          if (value.out_id != null)
            values_arr.push(value.out_id);
          result.count += value.count;
          result.countUnique += value.countUnique;
        });
        var unique = values_arr.filter(function(item, i, ar){ return ar.indexOf(item) === i; });
        result.countUnique += unique.length;
        return result;
      }
    }
    map_reduce(map,reduce).out(inline: true).to_a.map{|d| d['value']} rescue []
  end

您在发出或保留的值中也不需要user_id,因为它已经是mapReduce的“关键”值。其余的更改认为“count”和“countUnique”都可以包含需要考虑的退出值,您只需在每次传递时将值重置为0。

然后当然如果“输入”已经通过“减少”传递,那么你不需要为“唯一性”过滤“out_id”值,因为你已经有了计数,现在包括了。因此,任何null值都不会添加到要计算的事物数组中,这也会“添加”到总数而不是替换它。

因此,减速器多次被调用。对于20个键值,输入可能不会被分割,这就是输入较少的样本有效的原因。对于更多的东西,那么相同键值的“组”将被拆分,这就是mapReduce优化大数据处理的方式。由于“减少”输出将再次发送回减速器,您需要注意您正在考虑在上一次传递中已经发送到输出的值。