MongoDB, 原子级操作
MongoDB, Atomic Level Operation
我想在 MongoDB 中询问一些与 findAndModify 相关的信息。
据我所知,查询是“按文档隔离的”。
这意味着如果我 运行 2 findAndModify 是这样的:
{a:1},{set:{status:"processing", engine:1}}
{a:1},{set:{status:"processing", engine:2}}
并且此查询可能会影响 2.000 个文档,因为有 2 个查询 (2engine) 那么可能某些文档将具有“engine:1”和其他一些“engine:2”。
我认为 findAndModify 不会隔离“第一个查询”。
为了隔离第一个查询,我需要使用 $isolated.
都是我写的吗?
更新 - 情景
想法是写一个邻近引擎。
collection 用户有 1000-2000-3000 个用户,即数百万。
1 - 按最近点“lng,lat”排序
2 - 在 NodeJS 中,我进行了一些我无法在 MongoDB 中进行的计算
3 - 现在我将把用户分组到“用户组”中,然后我写一个批量更新
当我有 2000-3000 个用户时,这个过程(从 1 到 3)需要时间。
所以我想并行多线程。
并行线程是指并行查询。
这可能是个问题,因为 Query3 可能占用 Query1 的一些用户。
如果发生这种情况,那么在第 (2) 点,我没有最近的用户,而是最近的“对于此查询”,因为可能另一个查询占用了其余用户。这可能会导致纽约的某些用户与洛杉矶的用户分组。
更新 2 - 场景
我有一个 collection 这样的:
{location:[lng,lat], name:"1",gender:"m", status:'undone'}
{location:[lng,lat], name:"2",gender:"m", status:'undone'}
{location:[lng,lat], name:"3",gender:"f", status:'undone'}
{location:[lng,lat], name:"4",gender:"f", status:'done'}
我应该能够做的是通过最近的分组来创建 'Group' 用户。每组有1男+1女。在上面的示例中,我期望只有 1 个组(用户 1+用户 3),因为有男性+女性并且彼此非常接近(用户 2 也是男性,但远离用户 3 和用户-4 也是女性,但状态为 'done',因此已处理)。
现在组已创建(只有 1 个组),因此 2 个用户被标记为 'done',另一个用户 2 被标记为 'undone' 以供将来操作。
我希望能够非常快速地管理 1000-2000-3000 个用户。
更新 3:来自社区
现在好了。我可以试着总结一下你的情况吗?根据您的数据,您希望根据彼此的接近程度将男性和女性条目“配对”在一起。据推测,您不想进行所有可能的匹配,而只是设置一个一般“建议”列表,假设最近的位置为每个用户提供 10 个。现在我必须愚蠢到看不到这件事的全部方向,但这是否总结了基本的初始问题陈述。处理每个用户,找到他们的“配对”,配对后将它们标记为“完成”,并通过组合将它们从其他配对中排除,其中完成了吗?
这是一个不平凡的问题,不容易解决。
首先,迭代方法(诚然这是我的第一个方法)可能会导致错误的结果。
鉴于我们有以下文件
{
_id: "A",
gender: "m",
location: { longitude: 0, latitude: 1 }
}
{
_id: "B",
gender: "f",
location: { longitude: 0, latitude: 3 }
}
{
_id: "C",
gender: "m",
location: { longitude: 0, latitude: 4 }
}
{
_id: "D",
gender: "f",
location: { longitude: 0, latitude: 9 }
}
通过迭代方法,我们现在将从 "A" 开始并计算最近的女性,这当然是 "B",距离为 2。但是,实际上,最近的女性男性和女性之间的距离为 1(从 "B" 到 "C" 的距离)。但即使我们找到了这个,那也会使另一个匹配项 "A" 和 "D" 的距离为 8,而对于我们之前的解决方案,"A" 的距离为只有 2 到 "B".
所以我们需要决定走哪条路
- 天真地遍历文档
- 找到匹配个体之间的最小距离总和(这本身并不容易解决),以便所有参与者在一起的行程最短。
- 仅匹配可接受距离内的参与者
- 在共同地标(例如城市)的一定半径内进行某种分而治之并匹配参与者
解决方案 1:天真地遍历文档
var users = db.collection.find(yourQueryToFindThe1000users);
// We can safely use an unordered op here,
// which has greater performance.
// Since we use the "done" array do keep track of
// the processed members, there is no drawback.
var pairs = db.pairs.initializeUnorderedBulkOp();
var done = new Array();
users.forEach(
function(currentUser){
if( done.indexOf(currentUser._id) == -1 ) { return; }
var genderToLookFor = ( currentUser.gender === "m" ) ? "f" : "m";
// using the $near operator,
// the returned documents automatically are sorted from nearest
// to farest, and since findAndModify returns only one document
// we get the closest matching partner.
var nearPartner = db.collection.findAndModify(
query: {
status: "undone",
gender: genderToLookFor,
$near: {
$geometry: {
type: "Point" ,
coordinates: currentUser.location
}
}
},
update: { $set: { "status":"done" } },
fields: { _id: 1}
);
// Obviously, the current use already is processed.
// However, we store it for simplifying the process of
// setting the processed users to done.
done.push(currentUser._id, nearPartner._id);
// We have a pair, so we store it in a bulk operation
pairs.insert({
_id:{
a: currentUser._id,
b: nearPartner._id
}
});
}
)
// Write the found pairs
pairs.execute();
// Mark all that are unmarked by now as done
db.collection.update(
{
_id: { $in: done },
status: "undone"
},
{
$set: { status: "done" }
},
{ multi: true }
)
解决方案 2:找到匹配项之间距离的最小总和
这将是理想的解决方案,但解决起来极其复杂。我们需要一种性别的所有成员,计算与另一种性别的所有成员的所有距离,并迭代所有可能的匹配集。在我们的示例中,它非常简单,因为对于任何给定的性别,只有 4 种组合。仔细想想,这可能至少是旅行商问题(MTSP?)的一个变体。如果我是对的,组合的数量应该是
对于所有 n>2,其中 n 是可能的对数。
因此
对于 n=10
和一个惊人的
对于 n=25
这是 7.755 千万亿(长尺度)或 7.755 七十亿(短尺度)。
虽然有解决此类问题的方法,但世界纪录是在使用大量硬件和相当棘手的算法的 25,000 个节点范围内。我认为出于所有实际目的,可以排除这种 "solution"。
解决方案 3
为了防止人们可能会以不可接受的距离匹配人们,这取决于您的用例,您可能希望根据人们与共同地标的距离(他们将要相遇的地方,例如下一个更大的城市)。
对于我们的示例,假设我们的城市位于 [0,2] 和 [0,7]。因此,城市之间的距离 (5) 必须是我们可接受的匹配范围。所以我们对每个城市进行查询
db.collection.find({
$near: {
$geometry: {
type: "Point" ,
coordinates: [ 2 , 0 ]
},
$maxDistance: 5
}, status: "done"
})
并天真地迭代结果。由于 "A" 和 "B" 将是结果集中的第一个,因此它们将被匹配并完成。 "C" 运气不好,没有女孩留给他。但是当我们对第二个城市进行相同的查询时,他获得了第二次机会。好吧,他的旅行时间变长了一点,但是嘿,他和 "D"!
有个约会
要找到各自的距离,请选取一组固定的城市(城镇、大都市区,无论规模大小),按位置对它们进行排序,并将每个城市的半径设置为与其近邻的两个距离中较大的一个。这样,您就会得到重叠区域。因此,即使在一个地方找不到匹配项,也可能在其他地方找到。
Iirc, Google 地图允许它根据城市的大小来抓取一个国家的城市。一个更简单的方法是让人们选择他们各自的城市。
备注
- 显示的代码尚未准备好生产,需要改进。
- 我建议使用 1 和 0,而不是使用 "m" 和 "f" 来表示性别:仍然可以轻松映射,但需要更少的 space 来保存。
- 状态也是如此。
- 我认为最后一个解决方案是最好的,优化了一些顽固的距离并保持了比赛的高机会。
我想在 MongoDB 中询问一些与 findAndModify 相关的信息。 据我所知,查询是“按文档隔离的”。
这意味着如果我 运行 2 findAndModify 是这样的:
{a:1},{set:{status:"processing", engine:1}}
{a:1},{set:{status:"processing", engine:2}}
并且此查询可能会影响 2.000 个文档,因为有 2 个查询 (2engine) 那么可能某些文档将具有“engine:1”和其他一些“engine:2”。
我认为 findAndModify 不会隔离“第一个查询”。 为了隔离第一个查询,我需要使用 $isolated.
都是我写的吗?
更新 - 情景
想法是写一个邻近引擎。 collection 用户有 1000-2000-3000 个用户,即数百万。
1 - 按最近点“lng,lat”排序 2 - 在 NodeJS 中,我进行了一些我无法在 MongoDB 中进行的计算 3 - 现在我将把用户分组到“用户组”中,然后我写一个批量更新
当我有 2000-3000 个用户时,这个过程(从 1 到 3)需要时间。 所以我想并行多线程。
并行线程是指并行查询。 这可能是个问题,因为 Query3 可能占用 Query1 的一些用户。 如果发生这种情况,那么在第 (2) 点,我没有最近的用户,而是最近的“对于此查询”,因为可能另一个查询占用了其余用户。这可能会导致纽约的某些用户与洛杉矶的用户分组。
更新 2 - 场景
我有一个 collection 这样的:
{location:[lng,lat], name:"1",gender:"m", status:'undone'}
{location:[lng,lat], name:"2",gender:"m", status:'undone'}
{location:[lng,lat], name:"3",gender:"f", status:'undone'}
{location:[lng,lat], name:"4",gender:"f", status:'done'}
我应该能够做的是通过最近的分组来创建 'Group' 用户。每组有1男+1女。在上面的示例中,我期望只有 1 个组(用户 1+用户 3),因为有男性+女性并且彼此非常接近(用户 2 也是男性,但远离用户 3 和用户-4 也是女性,但状态为 'done',因此已处理)。
现在组已创建(只有 1 个组),因此 2 个用户被标记为 'done',另一个用户 2 被标记为 'undone' 以供将来操作。
我希望能够非常快速地管理 1000-2000-3000 个用户。
更新 3:来自社区 现在好了。我可以试着总结一下你的情况吗?根据您的数据,您希望根据彼此的接近程度将男性和女性条目“配对”在一起。据推测,您不想进行所有可能的匹配,而只是设置一个一般“建议”列表,假设最近的位置为每个用户提供 10 个。现在我必须愚蠢到看不到这件事的全部方向,但这是否总结了基本的初始问题陈述。处理每个用户,找到他们的“配对”,配对后将它们标记为“完成”,并通过组合将它们从其他配对中排除,其中完成了吗?
这是一个不平凡的问题,不容易解决。
首先,迭代方法(诚然这是我的第一个方法)可能会导致错误的结果。
鉴于我们有以下文件
{
_id: "A",
gender: "m",
location: { longitude: 0, latitude: 1 }
}
{
_id: "B",
gender: "f",
location: { longitude: 0, latitude: 3 }
}
{
_id: "C",
gender: "m",
location: { longitude: 0, latitude: 4 }
}
{
_id: "D",
gender: "f",
location: { longitude: 0, latitude: 9 }
}
通过迭代方法,我们现在将从 "A" 开始并计算最近的女性,这当然是 "B",距离为 2。但是,实际上,最近的女性男性和女性之间的距离为 1(从 "B" 到 "C" 的距离)。但即使我们找到了这个,那也会使另一个匹配项 "A" 和 "D" 的距离为 8,而对于我们之前的解决方案,"A" 的距离为只有 2 到 "B".
所以我们需要决定走哪条路
- 天真地遍历文档
- 找到匹配个体之间的最小距离总和(这本身并不容易解决),以便所有参与者在一起的行程最短。
- 仅匹配可接受距离内的参与者
- 在共同地标(例如城市)的一定半径内进行某种分而治之并匹配参与者
解决方案 1:天真地遍历文档
var users = db.collection.find(yourQueryToFindThe1000users);
// We can safely use an unordered op here,
// which has greater performance.
// Since we use the "done" array do keep track of
// the processed members, there is no drawback.
var pairs = db.pairs.initializeUnorderedBulkOp();
var done = new Array();
users.forEach(
function(currentUser){
if( done.indexOf(currentUser._id) == -1 ) { return; }
var genderToLookFor = ( currentUser.gender === "m" ) ? "f" : "m";
// using the $near operator,
// the returned documents automatically are sorted from nearest
// to farest, and since findAndModify returns only one document
// we get the closest matching partner.
var nearPartner = db.collection.findAndModify(
query: {
status: "undone",
gender: genderToLookFor,
$near: {
$geometry: {
type: "Point" ,
coordinates: currentUser.location
}
}
},
update: { $set: { "status":"done" } },
fields: { _id: 1}
);
// Obviously, the current use already is processed.
// However, we store it for simplifying the process of
// setting the processed users to done.
done.push(currentUser._id, nearPartner._id);
// We have a pair, so we store it in a bulk operation
pairs.insert({
_id:{
a: currentUser._id,
b: nearPartner._id
}
});
}
)
// Write the found pairs
pairs.execute();
// Mark all that are unmarked by now as done
db.collection.update(
{
_id: { $in: done },
status: "undone"
},
{
$set: { status: "done" }
},
{ multi: true }
)
解决方案 2:找到匹配项之间距离的最小总和
这将是理想的解决方案,但解决起来极其复杂。我们需要一种性别的所有成员,计算与另一种性别的所有成员的所有距离,并迭代所有可能的匹配集。在我们的示例中,它非常简单,因为对于任何给定的性别,只有 4 种组合。仔细想想,这可能至少是旅行商问题(MTSP?)的一个变体。如果我是对的,组合的数量应该是
对于所有 n>2,其中 n 是可能的对数。
因此
对于 n=10
和一个惊人的
对于 n=25
这是 7.755 千万亿(长尺度)或 7.755 七十亿(短尺度)。 虽然有解决此类问题的方法,但世界纪录是在使用大量硬件和相当棘手的算法的 25,000 个节点范围内。我认为出于所有实际目的,可以排除这种 "solution"。
解决方案 3
为了防止人们可能会以不可接受的距离匹配人们,这取决于您的用例,您可能希望根据人们与共同地标的距离(他们将要相遇的地方,例如下一个更大的城市)。
对于我们的示例,假设我们的城市位于 [0,2] 和 [0,7]。因此,城市之间的距离 (5) 必须是我们可接受的匹配范围。所以我们对每个城市进行查询
db.collection.find({
$near: {
$geometry: {
type: "Point" ,
coordinates: [ 2 , 0 ]
},
$maxDistance: 5
}, status: "done"
})
并天真地迭代结果。由于 "A" 和 "B" 将是结果集中的第一个,因此它们将被匹配并完成。 "C" 运气不好,没有女孩留给他。但是当我们对第二个城市进行相同的查询时,他获得了第二次机会。好吧,他的旅行时间变长了一点,但是嘿,他和 "D"!
有个约会要找到各自的距离,请选取一组固定的城市(城镇、大都市区,无论规模大小),按位置对它们进行排序,并将每个城市的半径设置为与其近邻的两个距离中较大的一个。这样,您就会得到重叠区域。因此,即使在一个地方找不到匹配项,也可能在其他地方找到。
Iirc, Google 地图允许它根据城市的大小来抓取一个国家的城市。一个更简单的方法是让人们选择他们各自的城市。
备注
- 显示的代码尚未准备好生产,需要改进。
- 我建议使用 1 和 0,而不是使用 "m" 和 "f" 来表示性别:仍然可以轻松映射,但需要更少的 space 来保存。
- 状态也是如此。
- 我认为最后一个解决方案是最好的,优化了一些顽固的距离并保持了比赛的高机会。