我有一个存储在mongodb中的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秒(过程函数非常简单,运行时间很短)。处理200万用户是不可行的。有什么建议加快$或查询?因为这似乎是最耗时的一步。
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
}
这是收集统计数据:
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
}
我正在运行带有125GB RAM的Ubuntu服务器。
请注意,我只会运行一次此分析(不是我会做的定期事情)。
答案 0 :(得分:1)
如果caller_id
和receiver_id
上的索引是单个复合索引,则此查询将执行集合扫描而不是索引扫描。确保它们都是单独索引的一部分,即:
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()
如果解释计划将其光标类型返回为BTreeCursor,则表示您正在使用索引扫描。如果它显示BasicCursor,那么你正在进行收集扫描,这是不好的。
知道每个索引的大小也很有趣。为了获得最佳查询性能,两个索引都应该完全加载到RAM中。如果索引太大以至于只有一个(或两个!)都不适合RAM,则必须从磁盘中将它们分页以查找结果。如果它们太大而不适合你的RAM,你的选择不是太大,基本上要么以某种方式拆分你的集合并重新索引它,或者获得更多的RAM。你总是可以为了这个分析而获得一个AWS RAM大量的实例,因为这是一次性的事情。
答案 1 :(得分:1)
我不是MongoDB的专家,虽然我有类似的问题&以下解决方案帮助我解决了这个问题。希望它也能帮到你。
查询使用索引并扫描确切的文档,因此您的索引没有问题,但我建议您:
首先尝试查看命令的状态:mongostat --discover
查看page faults
&等参数。 index miss
。
您是否尝试过预热(首先执行查询后执行查询)?热身后的表现是什么?如果它与前一个相同,则可能存在页面错误。
如果您打算将其作为分析运行,我认为预热数据库可能会对您有所帮助。
答案 2 :(得分:0)
我不知道为什么你的方法太慢了。
但您可能想尝试这些替代方法:
$in
多个ID。我不确定mongodb是否能很好地处理数百万个值,但如果没有,请对ID列表进行排序,然后将其拆分为批次。