如何重构Mongo文档以实际命中索引?

时间:2012-03-19 00:09:54

标签: ruby mongodb mongoid

理想情况下,我有一个Mongo文档,如下所示。我希望能够查询任何两个属性,然后按第三个排序。

文件:

{

 "tags" => ["ads", "shopping", "web20", "newspaper", "others..."],
 "reachable_via" => ["email", "twitter", "facebook", "contact_form", "phone"],
 "keywords" => ["keyword1", "keyword2", "keyword3"], 
 "score" => 4 #scalar of 0 - 10,
 "read_in_project_ids => [124, 433,556]

}

示例查询,使用Mongoid语法:

Document.any_in(:keywords => ["keyword1", "keyword2"]).where(:tags.in => ["ads", "shopping"], :reachable_via.in => ["email"]).order_by([:presence_score, :desc]).limit(10)

此查询有效,但它们不使用索引。另外,我试图对这个东西进行重组,使它以三种不同的方式工作,没有任何运气。

现在,我有380万个文档,这个查询可能需要45-60秒才能返回。

那么,我应该如何进行重组以保持一组数组字段的灵活性,同时获得索引优势?

仅供参考,关键字可能长达数百(并由用户添加),但标签和reachable_via元素是固定的(7个选项会增长),标签大约有20个选项会增长,并由应用程序的代码控制。

谢谢!

2 个答案:

答案 0 :(得分:1)

问题是$ in加上排序。

如果你可以删除其中一个,那么它会显着加快你的查询速度。

由于您不能拥有多个具有数组值键的索引(多键,因为它们会调用它们),因此您希望从查询中选择最精细的数组进行索引。在您的示例查询中,可能是关键字。

因此,为了使您的查询更快一点,您可以在{keywords:1,得分:-1}上放置一个索引。这将扫描关键字索引,过滤掉标签和reachable_via上的其他查询要求,然后按降序排序。我用500万个类似文档的集合测试了这个,并且它使用了实际上做得很好的过滤值的索引。

这是来自mongo shell的示例查询(对不起,我不是一个mongoid专家):

> db.test.find({keywords:{$in:["keyword15", "keyword18"]}, tags:{$in:["shopping","web20"]}, reachable_via:{$in:["email"]}}).sort({score:-1}).limit(10).explain();
{
"cursor" : "BtreeCursor keywords_1_score_-1 multi",
"nscanned" : 1750873,
"nscannedObjects" : 1750872,
"n" : 10,
"scanAndOrder" : true,
"millis" : 11999,
"nYields" : 0,
"nChunkSkips" : 0,
"isMultiKey" : true,
"indexOnly" : false,
"indexBounds" : {
    "keywords" : [
        [
            "keyword15",
            "keyword15"
        ],
        [
            "keyword18",
            "keyword18"
        ]
    ],
    "score" : [
        [
            {
                "$maxElement" : 1
            },
            {
                "$minElement" : 1
            }
        ]
    ]
}
}

如果您可以将查询更改为仅查询一个关键字,则可以更有效地使用索引,在0毫秒内获得特定关键字的前10个分数。

> db.test.find({keywords:"keyword15", tags:{$in:["shopping","web20"]}, reachable_via:{$in:["email"]}}).sort({score:-1}).limit(10).explain();
{
"cursor" : "BtreeCursor keywords_1_score_-1",
"nscanned" : 14,
"nscannedObjects" : 14,
"n" : 10,
"millis" : 0,
"nYields" : 0,
"nChunkSkips" : 0,
"isMultiKey" : true,
"indexOnly" : false,
"indexBounds" : {
    "keywords" : [
        [
            "keyword15",
            "keyword15"
        ]
    ],
    "score" : [
        [
            {
                "$maxElement" : 1
            },
            {
                "$minElement" : 1
            }
        ]
    ]
}
}

这是另一个例子。我将分数移出排序,并进入查询(查询确切的分数,没有限制)。如果您只是寻找最高分或类似的东西,这可以很好地加快查询速度。

> db.test.find({keywords:{$in:["keyword15", "keyword18"]}, tags:{$in:["shopping","web20"]}, reachable_via:{$in:["email"]}, score:9}).explain();
{
"cursor" : "BtreeCursor keywords_1_score_-1 multi",
"nscanned" : 175583,
"nscannedObjects" : 175581,
"n" : 82345,
"millis" : 999,
"nYields" : 0,
"nChunkSkips" : 0,
"isMultiKey" : true,
"indexOnly" : false,
"indexBounds" : {
    "keywords" : [
        [
            "keyword15",
            "keyword15"
        ],
        [
            "keyword18",
            "keyword18"
        ]
    ],
    "score" : [
        [
            9,
            9
        ]
    ]
}
}

冲洗,重复其他查询组合。在查询中选择最高粒度数组字段,将其与排序字段一起索引。如果您可以将查询限制为不在索引数组上使用$ in,那么这是理想的。

我的测试脚本位于: https://gist.github.com/2091880

测试脚本有一些缺点,例如几乎每个文档都有一个关键字1,所以事实证明查询关键字1,虽然它有一个索引,但是进行集合扫描的速度更快。无论如何,关于随机选择关键词,我只是有点懒,但在现实生活中这不会是一个问题。

答案 1 :(得分:0)

您需要建立您想要使用的索引。

http://www.mongodb.org/display/DOCS/Indexes

复合键部分就是您想要的。

如果您认为您的索引已正确建立,则可以为查询提供提示。

http://www.mongodb.org/display/DOCS/Optimization#Optimization-Hint