Показатель MongoDB 2dsphere $geoWithin

У меня есть коллекция с данными координат в форме 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. Найти с помощью индексной метки + 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"
}

Ответы

Ответ 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. Также похоже, что это будет в Dev build 2.5.3, который планируется выпустить в следующий вторник (10/15/2013).

Ответ 2

Что мне делать, чтобы заставить его работать предсказуемо, независимо от того, область?

$geoWithin просто не работает с эффективностью Θ (1). Насколько я понимаю, он будет работать со средним случаем эффективности Θ (n) (учитывая, что alg больше всего должен будет проверять n точек, как минимум, на 10).

Тем не менее, я бы определенно сделал некоторую предварительную обработку в коллекции координат, чтобы сначала обработать недавно добавленные координаты, чтобы дать вам больше шансов получить Θ (10) эффективность (и звучит так, как в дополнение к использованию position_2dsphere_timestamp_-1 - это путь)!

Некоторые предложили использовать {timestamp: -1, position: "2dsphere" } индекс, поэтому я тоже пробовал это, но он, похоже, не выполняет достаточно.

(см. ответ на начальный вопрос.)

Кроме того, может быть полезно следующее:

Стратегии оптимизации для MongoDB

Надеюсь, это поможет!

TL; DR вы можете обмануть индексы, которые вы хотите, но вы не получите большей эффективности из $geoWithin, если вы не перепишете его.

Говоря, вы всегда можете сосредоточиться на оптимизации производительности индекса и переписать эту функцию, если хотите!