Mongodb 如何为多个文档加速大型数组的聚合 总结
我有500万用户的每小时数据(10年=87600小时),例如每个用户每小时的点击次数。我试图聚合以获得每小时(所有用户)的总点击数,但它非常慢(超过5天) 非技术性解释 我问题的数据来源于熊猫。每个用户都由一个Mongodb 如何为多个文档加速大型数组的聚合 总结,mongodb,pymongo,Mongodb,Pymongo,我有500万用户的每小时数据(10年=87600小时),例如每个用户每小时的点击次数。我试图聚合以获得每小时(所有用户)的总点击数,但它非常慢(超过5天) 非技术性解释 我问题的数据来源于熊猫。每个用户都由一个用户ID标识,并有若干条与之相关的信息。为了说明的目的,我将考虑用户使用 USER ID 15512。我有一个User\u视图DataFrame,它有两列: Hour Views ----- ------ 11 5 15512 11 82215
用户ID
标识,并有若干条与之相关的信息。为了说明的目的,我将考虑用户使用<代码> USER ID 15512。我有一个User\u视图
DataFrame,它有两列:
Hour Views
----- ------
11 5
15512 11
82215 2
其中,小时
从固定的开始日期(2008年1月1日)开始计算。此表中仅包括用户具有非零视图的小时数。不幸的是,对于大多数用户来说,数据帧包含超过80000行
第二条数据是User\u点击次数
DataFrame,每个用户每小时点击次数:
Hour Clicks
----- ------
5511 1
34412 3
51241 2
66615 10
我想在所有用户中聚合视图
,以获得每小时的总视图数(在所有用户中)。我希望对单击操作执行相同操作
技术说明
为此,我使用以下模式将上述数据存储到MongoDB中:
schema = [
{
'User_ID': 15512,
'User_Name': 'Jack Daniels',
'Information_Type': 'Views',
'User_Views': [
{'Hour': 11, 'Views': 5},
{'Hour': 15512, 'Views': 11},
{'Hour': 82215, 'Views': 2},
]
},
{
'User_ID': 15512,
'User_Name': 'Jack Daniels',
'Information_Type': 'Clicks',
'User_Clicks': [
{'Hour': 5511, 'Clicks': 1},
{'Hour': 34412, 'Clicks': 3},
{'Hour': 51241, 'Clicks': 2},
{'Hour': 66615, 'Clicks': 10},
]
},
# The above is then repeated for every User_ID.
]
为了在模式中获得User\u视图
,我在Python中的User\u视图
DataFrame上做了df.to\u dict(orient='records')
。类似地,对于用户单击
理想情况下,我有User\u单击
和User\u视图
位于同一文档中,但我无法为该设置快速创建聚合,因此我从视图中拆分单击,如上面的模式所示
我已经为信息类型
编制了索引。我已经设置了allowDiskUse=True
我用于聚合视图的管道是:
pipeline = [
# Get only Views documents
{"$match": {"Information_Type": "Views"}},
# Return only Hours and Views
{"$project": {"User_Views.Hour": 1, "User_Views.Views": 1}},
# Unwind/flatten User_Views, since it's an array of documents
{"$unwind": "$User_Views"},
# Group by Hour across all documents and sum the Views
{"$group": {"_id": "$User_Views.Hour", "Total_Views": {"$sum": "$User_Views.Views"}}}
类似的点击
问题和目标
目前,500万用户和87600小时的运行时间大约为一周
我试着做两件事:
1) 通过深入了解模式和管道的改进,减少运行时间
2) 理想情况下,使用模式时,每个用户的视图和单击不会被分割到两个文档中。问题在于,您无法通过单个管道足够快地通过5mm用户X 87600条数据。当索引可以用来查找人口中相对较小的子集时,数据库(大多数)是非常棒的。当你想查看所有数据时,它们就没有那么好了。这个问题确实有地图/缩小的感觉
但在MongoDB中,实现这一点的一种方法是线程化并使用多个查询同时工作。每个线程的时间范围为小时
,例如,在伪代码中:
nthreads = 20
incr = 87600 / nthreads
curr = 0
while nthreads > 0:
exec_query (curr, curr+incr)
curr += incr
nthreads--
exec_query(start, end):
spawn{
db.foo.aggregate([
{$match: {'Information_Type': 'Views'}}
,{$project: {X: {$filter: {
input: "$User_Views",
as: "zz",
cond: {$and:[
{$gte: ['$$zz.Hour', start]}
,{$lt: ['$$zz.Hour', end]}
]
}
}}
}}
,{$unwind: "$X"}
,{$group: {_id:"$X.Hour", total: {$sum: "$X.Views"} }}
]);
会有大量数据读取,是的,$filter
操作符最终会抛出大量数据,但您将获得更多并行运行的$group
操作。问题是,您无法通过单个管道足够快地通过5mm用户X 87600条数据。当索引可以用来查找人口中相对较小的子集时,数据库(大多数)是非常棒的。当你想查看所有数据时,它们就没有那么好了。这个问题确实有地图/缩小的感觉
但在MongoDB中,实现这一点的一种方法是线程化并使用多个查询同时工作。每个线程的时间范围为小时
,例如,在伪代码中:
nthreads = 20
incr = 87600 / nthreads
curr = 0
while nthreads > 0:
exec_query (curr, curr+incr)
curr += incr
nthreads--
exec_query(start, end):
spawn{
db.foo.aggregate([
{$match: {'Information_Type': 'Views'}}
,{$project: {X: {$filter: {
input: "$User_Views",
as: "zz",
cond: {$and:[
{$gte: ['$$zz.Hour', start]}
,{$lt: ['$$zz.Hour', end]}
]
}
}}
}}
,{$unwind: "$X"}
,{$group: {_id:"$X.Hour", total: {$sum: "$X.Views"} }}
]);
将有大量数据读取,是的,$filter
操作符最终会抛出大量数据,但您将得到更多并行运行的$group
操作