Показатель 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
, если вы не перепишете его.
Говоря, вы всегда можете сосредоточиться на оптимизации производительности индекса и переписать эту функцию, если хотите!