我有一个包含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"
}
我已经看到了这个问题,因为我正在寻找类似的解决方案。这是一个很老的问题没有得到解决,如果其他人在寻找这种情况的解决方案,我将尝试解释为什么提到的方法不适合手头的任务以及如何微调这些查询。
在第一种情况下,扫描的物品很多是完全正常的。让我试着解释一下原因:
当Mongodb构建复合索引"position_2dsphere_timestamp_-1"
时,它实际上创建了一个B树来保存位置键中包含的所有几何,在本例中为Points,并且对于此B树中的每个不同值,创建另一个B树按降序保存时间戳。这意味着,除非您的条目非常(我的意思是非常)彼此接近,否则辅助B树只能容纳一个条目,并且查询性能几乎与在位置字段上具有索引相同。除了mongodb将能够使用辅助b树上的时间戳值而不是将实际文档带到内存并检查时间戳。
当我们构建复合索引"timestamp_-1_position_2dsphere"
时,这同样适用于其他场景。以毫秒精度同时输入两个条目是不太可能的。所以在这种情况下;是的,我们的数据按时间戳字段排序,但是我们有很多其他的B树只为每个不同的时间戳值保留一个条目。因此,应用geoWithin过滤器将无法很好地执行,因为它必须检查每个条目,直到达到限制。
那么如何才能使这些查询表现良好呢?就个人而言,我开始尽可能多地放置在地理空间领域前面的领域。但主要的伎俩是举行另一个字段让我们说“createdDay”,这将保持一天数精度。如果您需要更高的精度,您也可以使用小时精度,但是以性能为代价,这一切都取决于您的项目需求。你的索引看起来像这样:{createdDay:-1, position: "2dsphere"}
。现在,在同一天创建的每个文档都将在相同的2dsphere b-tree索引中存储和排序。因此,mongodb将从当前开始,因为它应该是索引中的最大值,并对创建日期为今天的文档的b树保持位置进行索引扫描。如果它找到至少10个文件,它将停止并返回这些文件,如果没有,它将移动到前一天,依此类推。这种方法应该大大提高你的性能。
我希望这对你的情况有所帮助。
您是否尝试在数据集上使用聚合框架?
您想要的查询类似于:
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中,计划于下周二(2013年10月15日)发布。
无论区域如何,我应该怎么做才能让它以可预测的方式执行?
$geoWithin
根本不能以Θ(1)效率运行。据我了解,它将以Θ(n)效率平均情况运行(考虑到alg最多需要检查n个点,至少10个)。
但是,我绝对会对坐标集进行一些预处理,以确保首先处理最近添加的坐标,以便更好地获得Θ(10)效率(除了使用position_2dsphere_timestamp_-1
之外,这样的声音会是要走的路))
有些人建议使用{timestamp:-1,position:“2dsphere”}索引,所以我也试过了,但它似乎表现不佳。
(请参阅对初始问题的回复。)
此外,以下可能是有用的!
Optimization Strategies for MongoDB
希望这可以帮助!
TL; DR你可以愚弄所有你想要的索引,但除非你重写它,否则你不会从$geoWithin
中获得更高的效率。
话虽如此,你可以随时专注于优化索引性能并重写函数,如果你愿意的话!