在 pymongo 中加速 $or 查询
Speeding up $or query in pymongo
我在 mongodb 中存储了 collection 的 18 亿条记录,其中每条记录如下所示:
{
"_id" : ObjectId("54c1a013715faf2cc0047c77"),
"service_type" : "JE",
"receiver_id" : NumberLong("865438083645"),
"time" : ISODate("2012-12-05T23:07:36Z"),
"duration" : 24,
"service_description" : "NQ",
"receiver_cell_id" : null,
"location_id" : "658_55525",
"caller_id" : NumberLong("475035504705")
}
我需要获取 200 万特定用户的所有记录(我在文本文件中有感兴趣的用户 ID)并在将结果写入数据库之前对其进行处理。我在 receiver_id 和 caller_id 上有索引(每个都是单个索引的一部分)。
我现在的流程是这样的:
for user in list_of_2million_users:
user_records = collection.find({ "$or" : [ { "caller_id": user }, { "receiver_id" : user } ] })
for record in user_records:
process(record)
但是,消耗user_records游标平均需要15秒(处理函数非常简单,运行ning时间低)。这将无法处理 200 万用户。有什么建议可以加快 $or 查询的速度吗?因为这似乎是最 time-consuming 的一步。
db.call_records.find({ "$or" : [ { "caller_id": 125091840205 }, { "receiver_id" : 125091840205 } ] }).explain()
{
"clauses" : [
{
"cursor" : "BtreeCursor caller_id_1",
"isMultiKey" : false,
"n" : 401,
"nscannedObjects" : 401,
"nscanned" : 401,
"scanAndOrder" : false,
"indexOnly" : false,
"nChunkSkips" : 0,
"indexBounds" : {
"caller_id" : [
[
125091840205,
125091840205
]
]
}
},
{
"cursor" : "BtreeCursor receiver_id_1",
"isMultiKey" : false,
"n" : 383,
"nscannedObjects" : 383,
"nscanned" : 383,
"scanAndOrder" : false,
"indexOnly" : false,
"nChunkSkips" : 0,
"indexBounds" : {
"receiver_id" : [
[
125091840205,
125091840205
]
]
}
}
],
"cursor" : "QueryOptimizerCursor",
"n" : 784,
"nscannedObjects" : 784,
"nscanned" : 784,
"nscannedObjectsAllPlans" : 784,
"nscannedAllPlans" : 784,
"scanAndOrder" : false,
"nYields" : 753,
"nChunkSkips" : 0,
"millis" : 31057,
"server" : "some_server:27017",
"filterSet" : false
}
这是 collection 统计数据:
db.call_records.stats()
{
"ns" : "stc_cdrs.call_records",
"count" : 1825338618,
"size" : 438081268320,
"avgObjSize" : 240,
"storageSize" : 468641284752,
"numExtents" : 239,
"nindexes" : 3,
"lastExtentSize" : 2146426864,
"paddingFactor" : 1,
"systemFlags" : 0,
"userFlags" : 1,
"totalIndexSize" : 165290709024,
"indexSizes" : {
"_id_" : 73450862016,
"caller_id_1" : 45919923504,
"receiver_id_1" : 45919923504
},
"ok" : 1
}
我 运行宁 Ubuntu 服务器有 125GB 内存。
请注意,我只会 运行 此分析一次(我不会定期进行)。
如果 caller_id
和 receiver_id
上的索引是单个复合索引,则此查询将执行 collection 扫描而不是索引扫描。确保它们都是单独索引的一部分,即:
db.user_records.ensureIndex({caller_id:1})
db.user_records.ensureIndex({receiver_id:1})
您可以在 mongo shell:
中确认您的查询正在执行索引扫描
db.user_records.find({'$or':[{caller_id:'example'},{receiver_id:'example'}]}).explain()
如果解释计划returns其游标类型为 BTreeCursor,则您正在使用索引扫描。如果显示 BasicCursor,则表示您正在进行 collection 扫描,这并不好。
了解每个索引的大小也很有趣。为了获得最佳查询性能,两个索引都应完全加载到 RAM 中。如果索引太大以至于只有一个(或两个都不!)适合 RAM,您将不得不从磁盘将它们分页以查找结果。如果它们太大而无法放入您的 RAM,您的选择就不会太多,基本上要么以某种方式拆分您的 collection 并 re-indexing,要么获得更多 RAM。你总是可以得到一个 AWS RAM-heavy 实例只是为了这个分析的目的,因为这是一个 one-off 的东西。
我不知道为什么你的方法这么慢。
但您可能想尝试这些替代方法:
- 一次对多个 ID 使用
$in
。我不确定 mongodb 是否能很好地处理数百万个值,但如果不能,请对 ID 列表进行排序,然后将其分成几批。
- 在应用程序中进行集合扫描,并根据包含感兴趣 ID 的哈希集检查每个条目。一次性脚本的性能应该可以接受,特别是因为您对这么多 ID 感兴趣。
我不是 MongoDB 方面的专家,尽管我遇到了类似的问题并且以下解决方案帮助我解决了这个问题。希望对你也有帮助。
查询正在使用索引并扫描准确的文档,因此您的索引没有问题,但我建议您:
首先尝试查看命令的状态:mongostat --discover
参见 page faults
& index miss
等参数。
您是否尝试过预热(先执行查询后的查询性能)?热身后的表现如何?如果与上一个相同,则可能是页面错误。
如果您打算 运行 将其作为分析,我认为预热数据库可能会对您有所帮助。
我在 mongodb 中存储了 collection 的 18 亿条记录,其中每条记录如下所示:
{
"_id" : ObjectId("54c1a013715faf2cc0047c77"),
"service_type" : "JE",
"receiver_id" : NumberLong("865438083645"),
"time" : ISODate("2012-12-05T23:07:36Z"),
"duration" : 24,
"service_description" : "NQ",
"receiver_cell_id" : null,
"location_id" : "658_55525",
"caller_id" : NumberLong("475035504705")
}
我需要获取 200 万特定用户的所有记录(我在文本文件中有感兴趣的用户 ID)并在将结果写入数据库之前对其进行处理。我在 receiver_id 和 caller_id 上有索引(每个都是单个索引的一部分)。
我现在的流程是这样的:
for user in list_of_2million_users:
user_records = collection.find({ "$or" : [ { "caller_id": user }, { "receiver_id" : user } ] })
for record in user_records:
process(record)
但是,消耗user_records游标平均需要15秒(处理函数非常简单,运行ning时间低)。这将无法处理 200 万用户。有什么建议可以加快 $or 查询的速度吗?因为这似乎是最 time-consuming 的一步。
db.call_records.find({ "$or" : [ { "caller_id": 125091840205 }, { "receiver_id" : 125091840205 } ] }).explain()
{
"clauses" : [
{
"cursor" : "BtreeCursor caller_id_1",
"isMultiKey" : false,
"n" : 401,
"nscannedObjects" : 401,
"nscanned" : 401,
"scanAndOrder" : false,
"indexOnly" : false,
"nChunkSkips" : 0,
"indexBounds" : {
"caller_id" : [
[
125091840205,
125091840205
]
]
}
},
{
"cursor" : "BtreeCursor receiver_id_1",
"isMultiKey" : false,
"n" : 383,
"nscannedObjects" : 383,
"nscanned" : 383,
"scanAndOrder" : false,
"indexOnly" : false,
"nChunkSkips" : 0,
"indexBounds" : {
"receiver_id" : [
[
125091840205,
125091840205
]
]
}
}
],
"cursor" : "QueryOptimizerCursor",
"n" : 784,
"nscannedObjects" : 784,
"nscanned" : 784,
"nscannedObjectsAllPlans" : 784,
"nscannedAllPlans" : 784,
"scanAndOrder" : false,
"nYields" : 753,
"nChunkSkips" : 0,
"millis" : 31057,
"server" : "some_server:27017",
"filterSet" : false
}
这是 collection 统计数据:
db.call_records.stats()
{
"ns" : "stc_cdrs.call_records",
"count" : 1825338618,
"size" : 438081268320,
"avgObjSize" : 240,
"storageSize" : 468641284752,
"numExtents" : 239,
"nindexes" : 3,
"lastExtentSize" : 2146426864,
"paddingFactor" : 1,
"systemFlags" : 0,
"userFlags" : 1,
"totalIndexSize" : 165290709024,
"indexSizes" : {
"_id_" : 73450862016,
"caller_id_1" : 45919923504,
"receiver_id_1" : 45919923504
},
"ok" : 1
}
我 运行宁 Ubuntu 服务器有 125GB 内存。
请注意,我只会 运行 此分析一次(我不会定期进行)。
如果 caller_id
和 receiver_id
上的索引是单个复合索引,则此查询将执行 collection 扫描而不是索引扫描。确保它们都是单独索引的一部分,即:
db.user_records.ensureIndex({caller_id:1})
db.user_records.ensureIndex({receiver_id:1})
您可以在 mongo shell:
中确认您的查询正在执行索引扫描db.user_records.find({'$or':[{caller_id:'example'},{receiver_id:'example'}]}).explain()
如果解释计划returns其游标类型为 BTreeCursor,则您正在使用索引扫描。如果显示 BasicCursor,则表示您正在进行 collection 扫描,这并不好。
了解每个索引的大小也很有趣。为了获得最佳查询性能,两个索引都应完全加载到 RAM 中。如果索引太大以至于只有一个(或两个都不!)适合 RAM,您将不得不从磁盘将它们分页以查找结果。如果它们太大而无法放入您的 RAM,您的选择就不会太多,基本上要么以某种方式拆分您的 collection 并 re-indexing,要么获得更多 RAM。你总是可以得到一个 AWS RAM-heavy 实例只是为了这个分析的目的,因为这是一个 one-off 的东西。
我不知道为什么你的方法这么慢。
但您可能想尝试这些替代方法:
- 一次对多个 ID 使用
$in
。我不确定 mongodb 是否能很好地处理数百万个值,但如果不能,请对 ID 列表进行排序,然后将其分成几批。 - 在应用程序中进行集合扫描,并根据包含感兴趣 ID 的哈希集检查每个条目。一次性脚本的性能应该可以接受,特别是因为您对这么多 ID 感兴趣。
我不是 MongoDB 方面的专家,尽管我遇到了类似的问题并且以下解决方案帮助我解决了这个问题。希望对你也有帮助。
查询正在使用索引并扫描准确的文档,因此您的索引没有问题,但我建议您:
首先尝试查看命令的状态:mongostat --discover
参见 page faults
& index miss
等参数。
您是否尝试过预热(先执行查询后的查询性能)?热身后的表现如何?如果与上一个相同,则可能是页面错误。
如果您打算 运行 将其作为分析,我认为预热数据库可能会对您有所帮助。