15

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

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

无论区域如何,我应该怎么做才能让它以可预测的方式执行?

一、收藏统计:

> 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

3 回答 3

4

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

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

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

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

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

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

于 2019-03-20T21:53:07.830 回答
1

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

您想要的查询类似于:

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 中。

于 2013-10-09T22:12:06.993 回答
1

无论区域如何,我应该怎么做才能让它以可预测的方式执行?

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

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

有些人建议使用 {timestamp: -1, position: "2dsphere"} 索引,所以我也尝试过,但它似乎表现得不够好。

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

此外,以下内容可能有用!

MongoDB 的优化策略

希望这可以帮助!

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

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

于 2014-10-10T22:28:20.047 回答