MongoDB,原子级操作

时间:2015-08-29 09:58:08

标签: mongodb node-mongodb-native

我想在MongoDB中询问一些与findAndModify相关的信息。 据我所知,查询是“按文档隔离”。

这意味着如果我像这样运行2个findAndModify:

{a:1},{set:{status:"processing", engine:1}}
{a:1},{set:{status:"processing", engine:2}}

并且此查询可能会影响2.000个文档,因为有2个查询(2引擎),然后可能某个文档将具有“engine:1”和另一个“engine:2”。

我认为findAndModify不会隔离“第一个查询”。 为了隔离第一个查询,我需要使用$ isolated。

一切都写了我写的东西吗?

更新 - 方案

想法是编写一个接近引擎。 用户拥有1000-2000-3000个用户,或数百万用户。

1 - 从最近点“lng,lat”开始排序 2 - 在NodeJS中,我做了一些我不能在MongoDB中进行的计算 3 - 现在我将用户分组为“UserGroup”,我写了一个批量更新

当我有2000-3000个用户时,这个过程(从1到3)需要时间。 所以我想要并行使用多线程。

并行线程意味着并行查询。 这可能是一个问题,因为Query3可以占用Query1的一些用户。 如果发生这种情况,那么在第(2)点,我没有最接近的用户,但最近的“对于此查询”,因为可能另一个查询占用了其余的用户。这可能会造成纽约的一些用户与洛杉矶的用户分组。

更新2 - 方案

我有一个这样的集合:

{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'}

我应该做的是通过最近的分组创建“组”用户。每组有1个男性+ 1个女性。在上面的例子中,我期望只有一个组(user1 + user3),因为有男性+女性并且彼此非常接近(用户2也是男性,但远离用户3和用户) -4也是女性,但状态为“已完成”,因此已经处理完毕。

现在创建了组(仅1组),因此2个用户被标记为“已完成”,而另一个用户2被标记为“撤消”以供将来操作。

我希望能够非常快速地管理1000-2000-3000个用户。

更新3:来自社区 好的,现在。我可以试着总结一下你的情况。根据您的数据,您希望根据彼此的接近程度将男性和女性条目“配对”在一起。大概你不想做所有可能的匹配,只是设置一般的“推荐”列表,让每个用户按最近的位置说10。现在我不得不愚蠢地看不到这方面的全部方向,但这总结了基本的初始问题陈述。处理每个用户,找到他们的“配对”,一旦配对就将它们标记为“已完成”,并通过组合完成将其排除在其他配对之外?

1 个答案:

答案 0 :(得分:2)

这是一个非常重要的问题,无法轻易解决。

首先,迭代方法(无可否认是我的第一个方法)可能会导致错误的结果。

鉴于我们有以下文件

{
   _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. 天真地遍历文件
  2. 找出匹配个体之间的最小距离总和(这本身并不容易解决),以便所有参与者一起旅行最短。
  3. 仅匹配可接受距离内的参与者
  4. 在一个共同的地标(例如城市)的某个半径内做某种划分和征服并匹配参与者
  5. 解决方案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的

    number of combinations,其中n是可能的对数。

    因此

    对于n = 10

    combinations for n=10

    和令人惊讶的

    对于n = 25

    combinations for n=25

    那是7.755千万亿(长尺度)或7.755 septillion(短尺度)。 虽然有解决此类问题的方法,但世界纪录在25,000个节点的范围内,使用大量硬件和非常棘手的算法。我认为,出于所有实际目的,可以排除这种“解决方案”。

    解决方案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,谷歌地图允许它根据他们的大小抓住一个国家的城市。一种更简单的方法是让人们选择他们各自的城市。

    注释

    1. 显示的代码不是生产就绪,需要改进。
    2. 我建议不要使用“m”和“f”来表示性别,而是建议使用1和0:仍然可以轻松映射,但需要更少的空间来保存。
    3. 状态相同。
    4. 我认为最后一个解决方案是最好的,以一种方式优化距离并保持匹配的机会。