Mongodb 在pymongo中加速$or查询

Mongodb 在pymongo中加速$or查询,mongodb,mongodb-query,pymongo,nosql,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" :

我在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),并在将结果写入数据库之前对其进行处理。我在接收方id和调用方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)
但是,使用用户_记录游标平均需要15秒(进程函数非常简单,运行时间很短)。这对于处理200万用户是不可行的。有没有加快$or查询速度的建议?因为这似乎是最耗时的一步

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内存的Ubuntu服务器


请注意,我将只运行此分析一次(而不是定期执行)

如果
caller\u id
receiver\u 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重实例,因为这是一件一次性的事情。

我不知道为什么您的方法如此缓慢

但您可能希望尝试以下替代方法:

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

  • 我不是MongoDB方面的专家,尽管我也遇到过类似的问题&以下解决方案帮助我解决了这个问题。希望它也能帮助你

    Query使用索引并扫描精确的文档,因此索引没有问题,不过我建议您:

    首先,尝试查看命令的状态:
    mongostat--discover

    有关
    页面错误
    索引未命中
    等参数,请参阅

    您是否尝试过预热(第一次执行查询后的查询性能)?热身后的表现如何?如果与前一个相同,则可能存在页面错误


    如果要将其作为分析运行,我认为预热数据库可能会对您有所帮助。

    两个键都是单独索引的一部分。我得到了一个光标类型。索引大小如下:“'indexSizes:{“id”:73450862016,“caller_id_1”:45919923504,“receiver_id_1”:45919923504,“caller_id_1_receiver_id_1”:576743216}我使用的服务器有大量的RAM。您是如何获得这些索引大小的?我有点不愿意相信这些是你索引的实际大小,因为每一个都是45G。我会考虑一个拥有32GB RAM的服务器有很多,但它甚至不能适应一个索引。要了解更多信息,我将引导您访问。我从查询的explain()中获取了它们。我使用的服务器有125G RAM可用。我更新了我的问题以包含这些内容。@JedEstep每个索引项大约25字节听起来并不令人难以置信。你能为一个
    用户记录
    查询发布解释吗?您的代码需要15秒才能使用的问题?编辑了“我的问题”以包含.explain()的输出结果集很小,查询已正确索引。您如何知道MongoDB/pymongo是这里的性能问题?你在mongod日志中看到缓慢的操作了吗?@wdberkeley是的。。从mongod日志中可以看出,获得结果几乎需要15秒:1)整个数据库有400GB,比RAM大得多。因此,虽然查询索引本身很快,但它需要从磁盘加载记录本身。2)考虑在数据库中使用短字段名称,并且只在反序列化期间将它们映射到有意义的名称。这将使集合的大小减少1/3左右。