我正在尝试使用 berlinMOD 数据和查询对 MongoDB 进行基准测试。为了表示单个车辆的行程(轨迹),我使用多个文档(基于点的表示)。样品:
[{
"_id": 3,
"tripid": 11,
"geom_lnglat": {
"type": "Point",
"coordinates": [
4.306468999999999,
50.873466799999974
]
},
"vehid": 2,
}, ...,
{
"_id": 0,
"tripid": 1,
"geom_lnglat": {
"type": "Point",
"coordinates": [
4.4579241,
50.88904930000001
]
},
"vehid": 1,
}]
我试图回答的问题是:“找到车辆去过的地方之间的最小距离”。意思是将一辆车的轨迹与所有其他车辆的轨迹进行比较,并找到它们之间的最小距离。
输出将包含 2 辆车的 id 以及它们之间的最小距离的文档,例如:
{
vehid: 3
vehid2: 1
distance: 9586.571537357966
}
问题:我面临的是,我可以在一小部分数据上成功运行查询,但是当我在包含 250 万个文档的集合上运行查询时,查询行为异常。该查询逐渐占用更多存储空间,4 天后占用了大约 1 TB,耗尽了存储空间,然后崩溃并出现存储错误。附言。我有 1 TB 存储驱动器。
我目前的疑问是:
[{ $lookup: {
// self-join with tmp_trips
from: "tmp_trips",
let: {
cur_tripid: "$tripid",
cur_vehid: "$vehid",
cur_point: "$geom_lnglat",
},
as: "result",
pipeline: [
{
$geoNear: {
near: "$$cur_point",
distanceField: "dist_from_point",
query: {
$expr: {
// Exclude documents with the same vehid
$lt: ["$vehid", "$$cur_vehid"],
},
},
spherical: true,
}, }, ], },
},
{ $unwind: {
path: "$result",
preserveNullAndEmptyArrays: false,
},
},
{ $sort:
{
"result.dist_from_point": 1,
vehid: 1,
"result.vehid": 1,
},
},
{ $group:
{
_id: {
vehid: "$vehid",
vehid2: "$result.vehid",
},
distance: {
$first: "$result.dist_from_point",
}, }, },]
它适用于小集合:
tmp_trips
但是当我将它移动到更大的集合时它不会收敛。
我运行了另一个版本的查询,其中的限制是查找彼此相距 10 公里以内的汽车,并且该查询在 26 分钟后返回结果。在此修改后的查询中,我在 maxDistance: 10
运算符中指定字段 $geoNear
。我的猜测是,这大大减少了 $results
运算符的 $lookup
数组中需要返回的集合。
所以我的问题是我可以以某种方式改进这个查询或者让它在整个查询由数据库管理的情况下工作吗?
我想也许如果我在Python中写一些for循环我也许能够执行这个查询,但是有更好的方法吗?
作为参考,具有 PostGIS 扩展的等效 Postgres 查询是:
SELECT L1.Licence AS Licence1, L2.Licence AS Licence2,
MIN(ST_Distance(T1.geom_point, T2.geom_point)) AS MinDist
FROM trip_postgis T1 INNER JOIN Querylicences L1 ON T1.vehid = L1.vehid,
trip_postgis T2 INNER JOIN Querylicences L2 ON T2.vehid = L2.vehid
WHERE L1.licence < L2.licence
GROUP BY L1.Licence, L2.Licence
ORDER BY L1.Licence, L2.Licence;
也许试试这个。在查找中,不需要计算两点之间的精确距离,只需要最短距离,因此毕达哥拉斯就足够了。然后选择距离最短的文档。如果需要,计算实际距离(以米为单位)
db.collection.aggregate([
{
$lookup: {
from: "collection",
as: "result",
let: {
vehid: "$vehid",
coordinates: "$geom_lnglat.coordinates"
},
pipeline: [
{
$match: {
$expr: {
$lt: [ "$vehid", "$$vehid" ]
}
}
},
{
$project: {
square_distance: {
// (lon_1 - lon_2)² + (lat_1 - lat_2)²
$add: [
{
$pow: [
{
$subtract: [
{ $first: "$$coordinates" },
{ $first: "$geom_lnglat.coordinates" }
]
},
2
]
},
{
$pow: [
{
$subtract: [
{ $last: "$$coordinates" },
{ $last: "$geom_lnglat.coordinates" }
]
},
2
]
}
]
}
}
}
]
}
},
// Take the shortest one
{
$set: {
result: {
$first: {
$sortArray: {
input: "$result",
sortBy: { square_distance: 1 }
}
}
}
}
},
// some cosmetic, e.g. calculate exact distance using Haversine formula as shown in my comment
{
$set: {
distance: {
$let: {
vars: {
dlon: { $degreesToRadians: { $subtract: [{ $first: "$geom_lnglat.coordinates" }, { $first: "$result.geom_lnglat.coordinates" }] } },
dlat: { $degreesToRadians: { $subtract: [{ $last: "$geom_lnglat.coordinates" }, { $last: "$result.geom_lnglat.coordinates" }] } },
lat1: { $degreesToRadians: { $last: "$geom_lnglat.coordinates" } },
lat2: { $degreesToRadians: { $last: "$result.geom_lnglat.coordinates" } }
},
in: {
// Haversine formula: sin²(dLat / 2) + sin²(dLon / 2) * cos(lat1) * cos(lat2);
$add: [
{ $pow: [{ $sin: { $divide: ["$$dlat", 2] } }, 2] },
{ $multiply: [{ $pow: [{ $sin: { $divide: ["$$dlon", 2] } }, 2] }, { $cos: "$$lat1" }, { $cos: "$$lat2" }] }
]
}
}
}
}
},
{
$set: {
distance: {
// Distance in Meters given by "6378.1 * 1000"
$multiply: [6378.1, 1000, 2, { $asin: { $sqrt: "$distance" } }]
}
}
}
])