【问题标题】:MongoDB 2dsphere index $geoWithin performanceMongoDB 2dsphere 索引 $geoWithin 性能
【发布时间】:2013-09-16 13:07:28
【问题描述】:

我有一个 GeoJSON Point 形式的坐标数据集合,我需要从中查询一个区域内的 10 个最新条目。现在有 1.000.000 个条目,但将增加大约 10 倍。

我的问题是,当所需区域内有很多条目时,我的查询性能会大大下降(案例 3)。我目前拥有的测试数据是随机的,但真实数据不会,因此无法仅根据区域的尺寸选择另一个索引(如案例 4)。

我应该怎么做才能让它无论在哪个区域都能按预期执行?

1.收集统计:

> db.randomcoordinates.stats()
{
    "ns" : "test.randomcoordinates",
    "count" : 1000000,
    "size" : 224000000,
    "avgObjSize" : 224,
    "storageSize" : 315006976,
    "numExtents" : 15,
    "nindexes" : 3,
    "lastExtentSize" : 84426752,
    "paddingFactor" : 1,
    "systemFlags" : 0,
    "userFlags" : 0,
    "totalIndexSize" : 120416128,
    "indexSizes" : {
        "_id_" : 32458720,
        "position_2dsphere_timestamp_-1" : 55629504,
        "timestamp_-1" : 32327904
    },
    "ok" : 1
}

2。索引:

> db.randomcoordinates.getIndexes()
[
    {
        "v" : 1,
        "key" : {
            "_id" : 1
        },
        "ns" : "test.randomcoordinates",
        "name" : "_id_"
    },
    {
        "v" : 1,
        "key" : {
            "position" : "2dsphere",
            "timestamp" : -1
        },
        "ns" : "test.randomcoordinates",
        "name" : "position_2dsphere_timestamp_-1"
    },
    {
        "v" : 1,
        "key" : {
            "timestamp" : -1
        },
        "ns" : "test.randomcoordinates",
        "name" : "timestamp_-1"
    }
]

3.使用 2dsphere 复合索引查找:

> db.randomcoordinates.find({position: {$geoWithin: {$geometry: {type: "Polygon", coordinates: [[[1, 1], [1, 90], [180, 90], [180, 1], [1, 1]]]}}}}).sort({timestamp: -1}).limit(10).hint("position_2dsphere_timestamp_-1").explain()
{
    "cursor" : "S2Cursor",
    "isMultiKey" : true,
    "n" : 10,
    "nscannedObjects" : 116775,
    "nscanned" : 283424,
    "nscannedObjectsAllPlans" : 116775,
    "nscannedAllPlans" : 283424,
    "scanAndOrder" : true,
    "indexOnly" : false,
    "nYields" : 4,
    "nChunkSkips" : 0,
    "millis" : 3876,
    "indexBounds" : {

    },
    "nscanned" : 283424,
    "matchTested" : NumberLong(166649),
    "geoTested" : NumberLong(166649),
    "cellsInCover" : NumberLong(14),
    "server" : "chan:27017"
}

4.使用时间戳索引查找:

> db.randomcoordinates.find({position: {$geoWithin: {$geometry: {type: "Polygon", coordinates: [[[1, 1], [1, 90], [180, 90], [180, 1], [1, 1]]]}}}}).sort({timestamp: -1}).limit(10).hint("timestamp_-1").explain()
{
    "cursor" : "BtreeCursor timestamp_-1",
    "isMultiKey" : false,
    "n" : 10,
    "nscannedObjects" : 63,
    "nscanned" : 63,
    "nscannedObjectsAllPlans" : 63,
    "nscannedAllPlans" : 63,
    "scanAndOrder" : false,
    "indexOnly" : false,
    "nYields" : 0,
    "nChunkSkips" : 0,
    "millis" : 0,
    "indexBounds" : {
        "timestamp" : [
            [
                {
                    "$maxElement" : 1
                },
                {
                    "$minElement" : 1
                }
            ]
        ]
    },
    "server" : "chan:27017"
}

有人建议使用{timestamp: -1, position: "2dsphere"} 索引,所以我也尝试了,但似乎效果不够好。

5.使用 Timestamp + 2dsphere 复合索引查找

> db.randomcoordinates.find({position: {$geoWithin: {$geometry: {type: "Polygon", coordinates: [[[1, 1], [1, 90], [180, 90], [180, 1], [1, 1]]]}}}}).sort({timestamp: -1}).limit(10).hint("timestamp_-1_position_2dsphere").explain()
{
    "cursor" : "S2Cursor",
    "isMultiKey" : true,
    "n" : 10,
    "nscannedObjects" : 116953,
    "nscanned" : 286513,
    "nscannedObjectsAllPlans" : 116953,
    "nscannedAllPlans" : 286513,
    "scanAndOrder" : true,
    "indexOnly" : false,
    "nYields" : 4,
    "nChunkSkips" : 0,
    "millis" : 4597,
    "indexBounds" : {

    },
    "nscanned" : 286513,
    "matchTested" : NumberLong(169560),
    "geoTested" : NumberLong(169560),
    "cellsInCover" : NumberLong(14),
    "server" : "chan:27017"
}

【问题讨论】:

  • 您能否澄清一下您所说的“因此不可能仅根据区域的尺寸选择另一个索引(如案例 4)。”?在我看来,无论区域大小如何,由于您只寻找最近的十个点,因此您总是会在时间戳索引中做得更好,其中 scanAndOrder 为假且 nscanned 最接近 n。鉴于此,我建议创建一个带有时间戳第一和位置第二的复合索引,但是,当前的 mongo 版本(2.4.6)不会以所需的方式使用它:jira.mongodb.org/browse/SERVER-10801
  • 该区域很大,而且集合中的坐标是随机的,说明有很多,所以时间戳索引更有效。当区域较小且条目较少时,使用时间戳索引时,当该区域少于10个时,需要遍历所有条目。在这种情况下,位置-时间戳复合索引显然是最快的,返回时间为 2 毫秒,而时间戳-位置索引将花费超过 2000 毫秒。我认为我需要(至少)针对不同类型的区域使用不同的索引。
  • 我认为您的评论成功了。如果您要在相当大的区域中查询最近的 10 个点,那么问题就从“查找一个区域中的所有点,然后找到最近的 10 个”到“遍历最近的条目并检查它们是否在该地区。”正如你所说,如果所有点的很大一部分都在多边形中,那么第二个会快得多。
  • 出于好奇,您能否在没有提示的情况下运行几次此类查询,然后对其运行解释以查看查询优化器使用的索引是什么? MongoDB 旨在为您的查询测试和选择最佳索引,因此您不必考虑这类事情(该功能可能会更好,但它在大多数情况下仍然有效)。
  • 使用初始索引时,它会选择 2dsphere-timestamp 复合索引,当我尝试在相当大的区域上运行它几次而没有提示时。所以它没有选择最佳的时间戳索引。

标签: mongodb geospatial


【解决方案1】:

我在寻找类似问题的解决方案时看到了这个问题。这是一个没有答案的非常古老的问题,如果其他人正在寻找解决此类情况的方法,我将尝试解释为什么提到的方法不适合手头的任务以及如何微调这些查询。

在第一种情况下,扫描这么多项目是完全正常的。让我试着解释一下原因:

当 Mongodb 构建复合索引 "position_2dsphere_timestamp_-1" 时,它实际上创建了一个 B-tree 来保存位置键中包含的所有几何图形,在本例中为 Points,并且对于此 B-tree 中的每个不同的值,另一个创建 B 树以按降序保存时间戳。这意味着,除非您的条目非常(我的意思是)彼此非常接近,否则二级 B 树将只保存一个条目,并且查询性能几乎与仅在位置字段上具有索引相同。除了 mongodb 将能够使用辅助 b 树上的时间戳值,而不是将实际文档带到内存并检查时间戳。

当我们构建复合索引"timestamp_-1_position_2dsphere" 时,这同样适用于其他场景。以毫秒精度同时输入两个条目是不太可能的。所以在这种情况下;是的,我们有我们的数据按时间戳字段排序,但是我们有很多其他的 B 树只为每个不同的时间戳值保存一个条目。因此,应用 geoWithin 过滤器的效果不会很好,因为它必须检查每个条目,直到达到限制。

那么如何才能使这类查询表现良好呢?就我个人而言,我首先在地理空间字段前面放置尽可能多的字段。但主要的技巧是保存另一个字段让我们说“createdDay”,它将保存一个以天为精度的数字。如果您需要更高的精度,您也可以使用小时级别的精度,但以性能为代价,这完全取决于您的项目需求。您的索引将如下所示:{createdDay:-1, position: "2dsphere"}。现在,在同一天创建的每个文档都将在同一个 2dsphere b-tree 索引上存储和排序。所以mongodb会从当天开始,因为它应该是索引中的最大值,并且对createdDay是今天的文档的b-tree持有位置进行索引扫描。如果它找到至少 10 个文档,它将停止并返回这些文档,否则它将移至前一天,依此类推。在您的情况下,这种方法应该会大大提高性能。

我希望这对你的情况有所帮助。

【讨论】:

  • 基于此:“除非您的条目非常,(我的意思是非常)彼此接近”,如果我在用于查询大区域的辅助点属性中近似项目的位置会发生什么?
  • 嗯,它肯定会以牺牲精度为代价来提高性能。但是使用这种方法,您要么会损失很多精度,要么无法获得足够的性能提升。相反,您可以尝试使用 Google 的 S2 算法在 MongoDb 的 B 树之上构建您的地理索引,这样您就可以完全控制精度和性能。
【解决方案2】:

您是否尝试过在数据集上使用聚合框架?

您想要的查询类似于:

db.randomcoordinates.aggregate(
    { $match: {position: {$geoWithin: {$geometry: {type: "Polygon", coordinates: [[[1, 1], [1, 90], [180, 90], [180, 1], [1, 1]]]}}}}},
    { $sort: { timestamp: -1 } },
    { $limit: 10 }
);

不幸的是,聚合框架在生产版本中还没有explain,所以你只会知道它是否会产生巨大的时间差异。如果你从源代码构建得很好,它看起来可能在上个月末就在那里:https://jira.mongodb.org/browse/SERVER-4504。它看起来也将在计划于下周二(2013 年 10 月 15 日)发布的 Dev build 2.5.3 中。

【讨论】:

    【解决方案3】:

    无论 地区?

    $geoWithin 根本无法以 Θ(1) 的效率运行。据我了解,它将在 Θ(n) 效率平均情况下运行(考虑到算法最多需要检查 n 个点,至少 10 个)。

    但是,我肯定会对坐标集合进行一些预处理,以确保首先处理最近添加的坐标,从而让您有更好的机会获得 Θ(10) 效率(除了使用position_2dsphere_timestamp_-1 将是要走的路)!

    有些人建议使用 {timestamp: -1, position: "2dsphere"} 索引,所以我也试过了,但它似乎没有执行 够了。

    (请参阅对最初问题的回复。)

    另外,以下可能有用!

    Optimization Strategies for MongoDB

    希望这会有所帮助!

    TL;DR 您可以随心所欲地使用索引,但除非您重写它,否则您不会从$geoWithin 中获得任何更高的效率。

    话虽如此,您始终可以只专注于优化索引性能并根据需要重写函数!

    【讨论】:

    • hleinone,这有帮助吗?
    猜你喜欢
    • 2019-04-02
    • 1970-01-01
    • 2016-07-24
    • 2013-06-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-25
    • 2014-03-25
    相关资源
    最近更新 更多