Pymongo 慢,聚合日期
Pymongo slow with aggregate on date
也许这是我的 igno运行ce 显示,但是当我的时间范围很小时我有一个查询出现得很快,但是一旦我 运行 一个不同日期的查询查询很快就停止了。看起来好像在日期(或时间戳)字段上匹配,即使它的索引不是很有效 - 或者我只是做错了。
数据格式如下:
alarm_data = {
"alarm_global_id": int,
"alarm_severity": int,
"alarm_date": float,
"created": float,
"new_status": bool,
"exp_day_status": False,
"exp_week_status": False,
"exp_month_status": False,
"exp_months_status": False,
"time_in_alarm": float,
}
我有以下索引:
db.events.create_index("alarm_global_id", name="alarm_global_id")
db.events.create_index([("new_status", ASCENDING)], name="new")
db.events.create_index([("alarm_date",DESCENDING), ("exp_day_status",DESCENDING)], name="exp_day")
db.events.create_index([("alarm_date",DESCENDING), ("exp_week_status",DESCENDING)], name="exp_week")
db.events.create_index([("alarm_date",DESCENDING), ("exp_month_status",DESCENDING)], name="exp_month")
db.events.create_index([("alarm_date",DESCENDING), ("exp_months_status",DESCENDING)], name="exp_months")
alarm_date 字段是一个时间戳 - 所以实际上是一个浮点数。也许真正的 datetime
对象排序更好?
无论如何,我们的想法是使用它作为一种计算长期聚合的方法~每分钟一次左右,而不执行巨大的完整收集扫描。方法是将所有new
数据作为增量,所有超过过期时间的数据作为负数,它们的总和是最后n秒的变化总和.处理新数据的方法是:
db.events.aggregate([
{ "$match": {"new_status":True}},
{ "$group": {"_id": "$alarm_global_id", "time":{"$sum":"$time_in_alarm"}, "count": {"$sum":1} } }
])
然后我们将 new_status
设置为 false,这样下次就找不到了。
为了计算天数,我们简单地匹配那些 alarm_date 小于 now - expiry
:
的非新的
db.events.aggregate([
{ "$match": {"exp_day_status":False, "alarm_date":{"$lt":time.time()-(60*60*24)}} },
{ "$group": {"_id": "$alarm_global_id", "time":{"$sum":"$time_in_alarm"}, "count": {"$sum":1} } }
])
同样,完成后我们将 exp_day_status 更新为 True
,以指示文档不需要再次包含在计算中。周、月和月版本重复相同的过程,只是更新了到期时间。
当我 运行 正在编写一个包含 60 documents/s 的测试并且 运行ges 被设置为 10 秒、30 秒、60 秒和 120 秒(而不是日、周、月,月值),计算和更新整个事情非常快~20-30ms - 即使当集合达到 3.5M 文档时,集合大小的计算速度也没有明显下降。
但是,一旦我将时间更改为 1 分钟、5 分钟、10 分钟和 20 分钟,事情就崩溃了。有趣的是,至少对我来说,每次到期时间的计算时间都很小,直到它超过阈值,然后计算变得非常慢。
每个阶段的计算时间如下:
DB SIZE: 5237
--------------------
calculation times (ms):
new: 3.04
day: 3.41
week: 1.00
month: 1.00
months: 0.96
update: 13.05
total: 24.05
DB SIZE: 28590
--------------------
calculation times (ms):
new: 4.00
day: 46.02
week: 39.00
month: 39.00
months: 39.01
update: 203.00
total: 370.03
**这里注意,日-月的所有计算结果都是0——没有新的结果,也没有符合条件的文档,所以为什么这么慢。然而,如果我有 10 秒、20 秒、30 秒和 60 秒的时间,它保持快速?
1 分钟一过去,一天的计算就会增加到 ~30 毫秒。当我们超过一周的时间段时,也会发生同样的情况——计算时间上升到 60 毫秒。如果我们尝试达到一个小时,那么它会非常大,因此每秒执行一次最终会花费超过一秒的时间。对我来说有趣的是,计算星期几的时间非常快(比如 1-2 毫秒),直到那个时间结束,然后它突然扫描整个集合或其他东西——如果它能准确地减少读取的数字,那肯定会将速度提高到性能良好的程度。
我确实理解这样的想法,即随着集合变大,查询时间会变长,但我可能天真地假设,如果我只返回 50 个结果进行严格查询,它不会增加到很多秒 30 分钟的结果,因为它应该快速拒绝任何比过期时间更新的内容,并且在过期字段上没有适当的布尔值,从而大大加快查询速度。
如果这完全是此设置的预期行为,请告诉我,我只是对任何系统要求太多来执行此任务。
更新
这是聚合的解释方法的输出:
{'explainVersion': '1',
'stages': [{'$cursor': {'queryPlanner': {'namespace': 'events.events',
'indexFilterSet': False,
'parsedQuery': {'$and': [{'exp_week_status': {'$eq': False}},
{'alarm_date': {'$lt': 1636715435.6099443}}]},
'queryHash': '6B9D5528',
'planCacheKey': '21EBBA73',
'maxIndexedOrSolutionsReached': False,
'maxIndexedAndSolutionsReached': False,
'maxScansToExplodeReached': False,
'winningPlan': {'stage': 'PROJECTION_SIMPLE',
'transformBy': {'alarm_global_id': 1, 'time_in_alarm': 1, '_id': 0},
'inputStage': {'stage': 'FETCH',
'filter': {'exp_week_status': {'$eq': False}},
'inputStage': {'stage': 'IXSCAN',
'keyPattern': {'alarm_date': 1, 'exp_day_status': -1},
'indexName': 'exp_day',
'isMultiKey': False,
'multiKeyPaths': {'alarm_date': [], 'exp_day_status': []},
'isUnique': False,
'isSparse': False,
'isPartial': False,
'indexVersion': 2,
'direction': 'forward',
'indexBounds': {'alarm_date': ['[-inf.0, 1636715435.609944)'],
'exp_day_status': ['[MaxKey, MinKey]']}}}},
'rejectedPlans': [{'stage': 'PROJECTION_SIMPLE',
'transformBy': {'alarm_global_id': 1, 'time_in_alarm': 1, '_id': 0},
'inputStage': {'stage': 'FETCH',
'inputStage': {'stage': 'IXSCAN',
'keyPattern': {'alarm_date': 1, 'exp_week_status': -1},
'indexName': 'exp_week',
'isMultiKey': False,
'multiKeyPaths': {'alarm_date': [], 'exp_week_status': []},
'isUnique': False,
'isSparse': False,
'isPartial': False,
'indexVersion': 2,
'direction': 'forward',
'indexBounds': {'alarm_date': ['[-inf.0, 1636715435.609944)'],
'exp_week_status': ['[false, false]']}}}},
{'stage': 'PROJECTION_SIMPLE',
'transformBy': {'alarm_global_id': 1, 'time_in_alarm': 1, '_id': 0},
'inputStage': {'stage': 'FETCH',
'filter': {'exp_week_status': {'$eq': False}},
'inputStage': {'stage': 'IXSCAN',
'keyPattern': {'alarm_date': 1, 'exp_month_status': -1},
'indexName': 'exp_month',
'isMultiKey': False,
'multiKeyPaths': {'alarm_date': [], 'exp_month_status': []},
'isUnique': False,
'isSparse': False,
'isPartial': False,
'indexVersion': 2,
'direction': 'forward',
'indexBounds': {'alarm_date': ['[-inf.0, 1636715435.609944)'],
'exp_month_status': ['[MaxKey, MinKey]']}}}},
{'stage': 'PROJECTION_SIMPLE',
'transformBy': {'alarm_global_id': 1, 'time_in_alarm': 1, '_id': 0},
'inputStage': {'stage': 'FETCH',
'filter': {'exp_week_status': {'$eq': False}},
'inputStage': {'stage': 'IXSCAN',
'keyPattern': {'alarm_date': 1, 'exp_months_status': -1},
'indexName': 'exp_months',
'isMultiKey': False,
'multiKeyPaths': {'alarm_date': [], 'exp_months_status': []},
'isUnique': False,
'isSparse': False,
'isPartial': False,
'indexVersion': 2,
'direction': 'forward',
'indexBounds': {'alarm_date': ['[-inf.0, 1636715435.609944)'],
'exp_months_status': ['[MaxKey, MinKey]']}}}}]}}},
{'$group': {'_id': '$alarm_global_id',
'time': {'$sum': '$time_in_alarm'},
'count': {'$sum': {'$const': 1}}}}],
'serverInfo': {'host': 'PC0V4SFH',
'port': 27017,
'version': '5.0.3',
'gitVersion': '657fea5a61a74d7a79df7aff8e4bcf0bc742b748'},
'serverParameters': {'internalQueryFacetBufferSizeBytes': 104857600,
'internalQueryFacetMaxOutputDocSizeBytes': 104857600,
'internalLookupStageIntermediateDocumentMaxSizeBytes': 104857600,
'internalDocumentSourceGroupMaxMemoryBytes': 104857600,
'internalQueryMaxBlockingSortMemoryUsageBytes': 104857600,
'internalQueryProhibitBlockingMergeOnMongoS': 0,
'internalQueryMaxAddToSetBytes': 104857600,
'internalDocumentSourceSetWindowFieldsMaxMemoryBytes': 104857600},
'command': {'aggregate': 'events',
'pipeline': [{'$match': {'alarm_date': {'$lt': 1636715435.6099443},
'exp_week_status': False}},
{'$group': {'_id': '$alarm_global_id',
'time': {'$sum': '$time_in_alarm'},
'count': {'$sum': 1}}}],
'explain': True,
'lsid': {'id': UUID('9fbae31f-dbc5-4b52-85f2-5bc7eba82bfc')},
'$db': 'events',
'$readPreference': {'mode': 'primaryPreferred'}},
'ok': 1.0}
所以这个解决方案对我来说很奇怪,这一定是有原因的,但是复合索引中字段的顺序很重要。
新索引有 $lt date
第二个和 status
第一个:
db.events.create_index([("exp_day_status",DESCENDING), ("alarm_date",DESCENDING)], name="exp_day")], name="exp_day")
只需更改顺序,使 exp_day_status
排在第一位,alarm_date
排在第二位,就完全改变了一切。超过 120 万个文档现在将近 6 个小时,计算时间自我们开始以来没有变化(除了每个部分随着年龄的增长而略有增加):
DB SIZE: 1244217
--------------------
FIELD TIME(ms) COUNT
new: 2.01 65
30mins: 1.05 64
1hr: 1.46 64
2hr: 0.37 63
months: 1.01 0
update: 14.03 256
total: 19.93
--------------------
avg: 29.27
后续问题是是否有更好的方法来构建整个流程。我们目前 运行 获取聚合方法,然后执行 update_many()
,它执行相同的查询。新的问题是,是否先执行基本的 find()
,然后使用该结果执行 update_one()
或 findOneAndUpdate()
,使用第一个找到的 _id
s 会更快?还是有一种我在 mongo 中没有遇到的方法可以直接将 find many 的输出通过管道传输到更新?
也许这是我的 igno运行ce 显示,但是当我的时间范围很小时我有一个查询出现得很快,但是一旦我 运行 一个不同日期的查询查询很快就停止了。看起来好像在日期(或时间戳)字段上匹配,即使它的索引不是很有效 - 或者我只是做错了。
数据格式如下:
alarm_data = {
"alarm_global_id": int,
"alarm_severity": int,
"alarm_date": float,
"created": float,
"new_status": bool,
"exp_day_status": False,
"exp_week_status": False,
"exp_month_status": False,
"exp_months_status": False,
"time_in_alarm": float,
}
我有以下索引:
db.events.create_index("alarm_global_id", name="alarm_global_id")
db.events.create_index([("new_status", ASCENDING)], name="new")
db.events.create_index([("alarm_date",DESCENDING), ("exp_day_status",DESCENDING)], name="exp_day")
db.events.create_index([("alarm_date",DESCENDING), ("exp_week_status",DESCENDING)], name="exp_week")
db.events.create_index([("alarm_date",DESCENDING), ("exp_month_status",DESCENDING)], name="exp_month")
db.events.create_index([("alarm_date",DESCENDING), ("exp_months_status",DESCENDING)], name="exp_months")
alarm_date 字段是一个时间戳 - 所以实际上是一个浮点数。也许真正的 datetime
对象排序更好?
无论如何,我们的想法是使用它作为一种计算长期聚合的方法~每分钟一次左右,而不执行巨大的完整收集扫描。方法是将所有new
数据作为增量,所有超过过期时间的数据作为负数,它们的总和是最后n秒的变化总和.处理新数据的方法是:
db.events.aggregate([
{ "$match": {"new_status":True}},
{ "$group": {"_id": "$alarm_global_id", "time":{"$sum":"$time_in_alarm"}, "count": {"$sum":1} } }
])
然后我们将 new_status
设置为 false,这样下次就找不到了。
为了计算天数,我们简单地匹配那些 alarm_date 小于 now - expiry
:
db.events.aggregate([
{ "$match": {"exp_day_status":False, "alarm_date":{"$lt":time.time()-(60*60*24)}} },
{ "$group": {"_id": "$alarm_global_id", "time":{"$sum":"$time_in_alarm"}, "count": {"$sum":1} } }
])
同样,完成后我们将 exp_day_status 更新为 True
,以指示文档不需要再次包含在计算中。周、月和月版本重复相同的过程,只是更新了到期时间。
当我 运行 正在编写一个包含 60 documents/s 的测试并且 运行ges 被设置为 10 秒、30 秒、60 秒和 120 秒(而不是日、周、月,月值),计算和更新整个事情非常快~20-30ms - 即使当集合达到 3.5M 文档时,集合大小的计算速度也没有明显下降。 但是,一旦我将时间更改为 1 分钟、5 分钟、10 分钟和 20 分钟,事情就崩溃了。有趣的是,至少对我来说,每次到期时间的计算时间都很小,直到它超过阈值,然后计算变得非常慢。
每个阶段的计算时间如下:
DB SIZE: 5237
--------------------
calculation times (ms):
new: 3.04
day: 3.41
week: 1.00
month: 1.00
months: 0.96
update: 13.05
total: 24.05
DB SIZE: 28590
--------------------
calculation times (ms):
new: 4.00
day: 46.02
week: 39.00
month: 39.00
months: 39.01
update: 203.00
total: 370.03
**这里注意,日-月的所有计算结果都是0——没有新的结果,也没有符合条件的文档,所以为什么这么慢。然而,如果我有 10 秒、20 秒、30 秒和 60 秒的时间,它保持快速?
1 分钟一过去,一天的计算就会增加到 ~30 毫秒。当我们超过一周的时间段时,也会发生同样的情况——计算时间上升到 60 毫秒。如果我们尝试达到一个小时,那么它会非常大,因此每秒执行一次最终会花费超过一秒的时间。对我来说有趣的是,计算星期几的时间非常快(比如 1-2 毫秒),直到那个时间结束,然后它突然扫描整个集合或其他东西——如果它能准确地减少读取的数字,那肯定会将速度提高到性能良好的程度。
我确实理解这样的想法,即随着集合变大,查询时间会变长,但我可能天真地假设,如果我只返回 50 个结果进行严格查询,它不会增加到很多秒 30 分钟的结果,因为它应该快速拒绝任何比过期时间更新的内容,并且在过期字段上没有适当的布尔值,从而大大加快查询速度。
如果这完全是此设置的预期行为,请告诉我,我只是对任何系统要求太多来执行此任务。
更新 这是聚合的解释方法的输出:
{'explainVersion': '1',
'stages': [{'$cursor': {'queryPlanner': {'namespace': 'events.events',
'indexFilterSet': False,
'parsedQuery': {'$and': [{'exp_week_status': {'$eq': False}},
{'alarm_date': {'$lt': 1636715435.6099443}}]},
'queryHash': '6B9D5528',
'planCacheKey': '21EBBA73',
'maxIndexedOrSolutionsReached': False,
'maxIndexedAndSolutionsReached': False,
'maxScansToExplodeReached': False,
'winningPlan': {'stage': 'PROJECTION_SIMPLE',
'transformBy': {'alarm_global_id': 1, 'time_in_alarm': 1, '_id': 0},
'inputStage': {'stage': 'FETCH',
'filter': {'exp_week_status': {'$eq': False}},
'inputStage': {'stage': 'IXSCAN',
'keyPattern': {'alarm_date': 1, 'exp_day_status': -1},
'indexName': 'exp_day',
'isMultiKey': False,
'multiKeyPaths': {'alarm_date': [], 'exp_day_status': []},
'isUnique': False,
'isSparse': False,
'isPartial': False,
'indexVersion': 2,
'direction': 'forward',
'indexBounds': {'alarm_date': ['[-inf.0, 1636715435.609944)'],
'exp_day_status': ['[MaxKey, MinKey]']}}}},
'rejectedPlans': [{'stage': 'PROJECTION_SIMPLE',
'transformBy': {'alarm_global_id': 1, 'time_in_alarm': 1, '_id': 0},
'inputStage': {'stage': 'FETCH',
'inputStage': {'stage': 'IXSCAN',
'keyPattern': {'alarm_date': 1, 'exp_week_status': -1},
'indexName': 'exp_week',
'isMultiKey': False,
'multiKeyPaths': {'alarm_date': [], 'exp_week_status': []},
'isUnique': False,
'isSparse': False,
'isPartial': False,
'indexVersion': 2,
'direction': 'forward',
'indexBounds': {'alarm_date': ['[-inf.0, 1636715435.609944)'],
'exp_week_status': ['[false, false]']}}}},
{'stage': 'PROJECTION_SIMPLE',
'transformBy': {'alarm_global_id': 1, 'time_in_alarm': 1, '_id': 0},
'inputStage': {'stage': 'FETCH',
'filter': {'exp_week_status': {'$eq': False}},
'inputStage': {'stage': 'IXSCAN',
'keyPattern': {'alarm_date': 1, 'exp_month_status': -1},
'indexName': 'exp_month',
'isMultiKey': False,
'multiKeyPaths': {'alarm_date': [], 'exp_month_status': []},
'isUnique': False,
'isSparse': False,
'isPartial': False,
'indexVersion': 2,
'direction': 'forward',
'indexBounds': {'alarm_date': ['[-inf.0, 1636715435.609944)'],
'exp_month_status': ['[MaxKey, MinKey]']}}}},
{'stage': 'PROJECTION_SIMPLE',
'transformBy': {'alarm_global_id': 1, 'time_in_alarm': 1, '_id': 0},
'inputStage': {'stage': 'FETCH',
'filter': {'exp_week_status': {'$eq': False}},
'inputStage': {'stage': 'IXSCAN',
'keyPattern': {'alarm_date': 1, 'exp_months_status': -1},
'indexName': 'exp_months',
'isMultiKey': False,
'multiKeyPaths': {'alarm_date': [], 'exp_months_status': []},
'isUnique': False,
'isSparse': False,
'isPartial': False,
'indexVersion': 2,
'direction': 'forward',
'indexBounds': {'alarm_date': ['[-inf.0, 1636715435.609944)'],
'exp_months_status': ['[MaxKey, MinKey]']}}}}]}}},
{'$group': {'_id': '$alarm_global_id',
'time': {'$sum': '$time_in_alarm'},
'count': {'$sum': {'$const': 1}}}}],
'serverInfo': {'host': 'PC0V4SFH',
'port': 27017,
'version': '5.0.3',
'gitVersion': '657fea5a61a74d7a79df7aff8e4bcf0bc742b748'},
'serverParameters': {'internalQueryFacetBufferSizeBytes': 104857600,
'internalQueryFacetMaxOutputDocSizeBytes': 104857600,
'internalLookupStageIntermediateDocumentMaxSizeBytes': 104857600,
'internalDocumentSourceGroupMaxMemoryBytes': 104857600,
'internalQueryMaxBlockingSortMemoryUsageBytes': 104857600,
'internalQueryProhibitBlockingMergeOnMongoS': 0,
'internalQueryMaxAddToSetBytes': 104857600,
'internalDocumentSourceSetWindowFieldsMaxMemoryBytes': 104857600},
'command': {'aggregate': 'events',
'pipeline': [{'$match': {'alarm_date': {'$lt': 1636715435.6099443},
'exp_week_status': False}},
{'$group': {'_id': '$alarm_global_id',
'time': {'$sum': '$time_in_alarm'},
'count': {'$sum': 1}}}],
'explain': True,
'lsid': {'id': UUID('9fbae31f-dbc5-4b52-85f2-5bc7eba82bfc')},
'$db': 'events',
'$readPreference': {'mode': 'primaryPreferred'}},
'ok': 1.0}
所以这个解决方案对我来说很奇怪,这一定是有原因的,但是复合索引中字段的顺序很重要。
新索引有 $lt date
第二个和 status
第一个:
db.events.create_index([("exp_day_status",DESCENDING), ("alarm_date",DESCENDING)], name="exp_day")], name="exp_day")
只需更改顺序,使 exp_day_status
排在第一位,alarm_date
排在第二位,就完全改变了一切。超过 120 万个文档现在将近 6 个小时,计算时间自我们开始以来没有变化(除了每个部分随着年龄的增长而略有增加):
DB SIZE: 1244217
--------------------
FIELD TIME(ms) COUNT
new: 2.01 65
30mins: 1.05 64
1hr: 1.46 64
2hr: 0.37 63
months: 1.01 0
update: 14.03 256
total: 19.93
--------------------
avg: 29.27
后续问题是是否有更好的方法来构建整个流程。我们目前 运行 获取聚合方法,然后执行 update_many()
,它执行相同的查询。新的问题是,是否先执行基本的 find()
,然后使用该结果执行 update_one()
或 findOneAndUpdate()
,使用第一个找到的 _id
s 会更快?还是有一种我在 mongo 中没有遇到的方法可以直接将 find many 的输出通过管道传输到更新?