我创建我的批处理并使用下面指定的命令
将其插入集合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} ...]
查询提供的数据在插入批次之前是准确的。
我无法绕过这里发生的事情。我错过了什么吗?
答案 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优化大数据处理的方式。由于“减少”输出将再次发送回减速器,您需要注意您正在考虑在上一次传递中已经发送到输出的值。