加速$或查询pymongo

时间:2015-02-13 23:09:37

标签: mongodb mongodb-query pymongo nosql

我有一个存储在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服务器。

请注意,我只会运行一次此分析(不是我会做的定期事情)。

3 个答案:

答案 0 :(得分:1)

如果caller_idreceiver_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)

我不知道为什么你的方法太慢了。

但您可能想尝试这些替代方法:

  1. 一次使用$in多个ID。我不确定mongodb是否能很好地处理数百万个值,但如果没有,请对ID列表进行排序,然后将其拆分为批次。
  2. 在应用程序中执行集合扫描,并根据包含有趣ID的哈希集检查每个条目。对于一次性脚本应该具有可接受的性能,特别是因为您对这么多ID感兴趣。