Mongoid通过inject或mapreduce模拟连接?

时间:2014-03-18 00:06:02

标签: ruby-on-rails mongodb mapreduce mongoid

免责声明:我是新手。

我想模拟我的mongodb嵌入式文档的连接。如果我有一个嵌入式列表:

{
  _id: ObjectId("5320f6c34b6576d373000000"),
  user_id: "52f581096b657612fe020000",
  list: "52f4fd9f52e39bc0c15674ea"
  {
    player_1: "52f4fd9f52e39bc0c15674ex",
    player_2: "52f4fd9f52e39bc0c15674ey",
    player_3: "52f4fd9f52e39bc0c15674ez"
  }
}

每个玩家的玩家收藏类似于:

{
  _id: ObjectId("52f4fd9f52e39bc0c15674ex"),
  college: "Louisville",
  headshot: "player.png",
  height: "6'2",
  name: "Wayne Brady",
  position: "QB",
  weight: 205
}

我想最终:

{
  _id: ObjectId("5320f6c34b6576d373000000"),
  user_id: "52f581096b657612fe020000",
  list: "52f4fd9f52e39bc0c15674ea"
  {
    player_1: 
    {
      _id: ObjectId("52f4fd9f52e39bc0c15674ex"),
      college: "Louisville",
      headshot: "player.png",
      height: "6'2",
      name: "Wayne Brady",
      position: "QB",
      weight: 205
    },
    etc...
  }
}

所以我可以致电User.lists.first.player_1.name

这是我脑子里有意义的事情,因为我是铁杆新手......而且我不想在每个用户的名单中嵌入玩家,因为我有这样的许多裁员......

么?这有可能,如果是这样的话怎么样?这是一个好主意,还是有更好的方法?

1 个答案:

答案 0 :(得分:1)

所以有一个典型的关系模型,让我们称之为“一对多”,你有用户或“用户团队”和一整套玩家。在典型的建模形式中,您希望“去规范化”以避免重复。

但是就是这样,MongoDB 没有加入。在当前用语中,联接不是“webscale”。所以它会让你思考该怎么做。或者 MongoDB是否会加入?

db.eval(function() {

      var user = db.user.findOne({ "user_id": "52f581096b657612fe020000" });

      for ( k in user.list ) {
          var player = db.player.findOne({ "_id": user.list[k] });
          user.list[k] = player;
      }

      return user;
});

哪个“可以说” “是一种加入”。这一切都是在服务器上完成的,对吗?

不要那样做。虽然db.eval()有用,但您要定期查询的内容并不是实际用途之一。阅读文档,其中显示警告。特别是,所有JavaScript都在一个线程中运行,因此可以非常快速地锁定。

现在客户端方面,您或多或少都在做同样的事情。你选择的ODM可能会再次“做同样的事情”,虽然它通常以某种方式隐藏它,所以你看不到那一部分。同样可以说你的SQL ORM也是如此,当你刚刚访问代码中的对象时,它也会“隐藏在你背后”并查询数据库。

mapReduce 。那么你提出的数据的问题是没有什么可以“减少”。有一种技术称为"incremental mapReduce",但它不适合这种类型的数据。一个话题本身,但你基本上需要与“玩家”相关联的所有“用户”,存储在“玩家数据”中,以实现任何类型的可行性。而这最终只是另一种“欺骗”联合的方式。

这是MongoDB存在的空间。

因此,它不是去做所有这些获取或加入,而是允许“预先加入”您的数据的概念。这样做的目的是允许更快,更原子的读写。这被称为嵌入

查看您的数据,根本不应该存在嵌入问题。考虑一下要点:

  • 据推测,您正在为给定用户建模“幻想团队”。如果“团队”不包含无数玩家,那将是公平的。

  • 除了其他内容,您的“A1”用法可能会“显示”与该“用户团队”相关联的玩家。在很多情况下,您希望“显示”尽可能多的信息,将其保留为单个读取操作。您还希望轻松地将“玩家”添加到“用户团队”。

  • 虽然“播放器”可能包含“扩展信息”,甚至可能包含某些全球统计信息或分数,但该信息可能不是您要访问的信息,而与通常是“用户团队”。它可能是独立编写的,只有在查看“播放器详细信息”时才会读取。

这是支持嵌入的三个好例子。当然,您将复制存储在每个用户团队中的信息,而不是仅仅是一个小的“关键”参考。确保信息可能存在于完整的“播放器细节”的其他地方,并且也会重复。

但这里“重复”的意思是优化。所以在这里嵌入“一些数据”似乎是有效的,而不是全部,但是你经常在主要操作中使用它。考虑到“玩家”的名字,位置,身高和体重不太可能定期改变,甚至根本不会改变,那么这似乎是一个合理的权衡。

{
    "_id": ObjectId("5320f6c34b6576d373000000"),
    "user_id": ObjectId("52f581096b657612fe020000"),
    "list": [
        {  
            "_id": ObjectId("52f4fd9f52e39bc0c15674ex"),
            "label": "Player1",
            "college": "Louisville",
            "headshot": "player.png",
            "height": "6'2",
            "name": "Wayne Brady",
            "position": "QB",
            "weight": 205
        },
        {
            "label": "Player2",
            (...)
        }
    ]
},

那不是那么糟糕。打破16MB limit需要很多时间。考虑到这似乎是一个“用户团队”,那么它也可以用来自“用户”的一些信息。

当数据保存在一起时,你也可以从中获得很多力量,找到每个用户挑选的顶级“玩家”:

db.userteams.aggregate([
    // Unwind the array
    { "$unwind": "$list" },

    // Group and use the player name
    { "$group": {
        "_id": {
            "user_id": "$user_id",
            "player": "$list.name",
        },
        "count": { "$sum": 1 }
    }},

    // Sort the results descending by popularity
    { "$sort": { "_id.user_id": 1, "count": -1 } },

    // Group to limit the first one 
    { "$group": {
        "_id": "$_id.user_id",
        "player": { "$first": "$_id.player" },
        "picks": { "$first:" "$count" }
    }}

])

在这种情况下,无可否认地使用了一个名称,但它是使用某些嵌入可以获得的信息的一个例子。

当然,你真的相信你需要对所有事情进行适当的规范化,然后以这种方式进行,并使用你需要访问它的模式。但这提供了另一种方式的视角。

所以不要过度关注嵌入所有内容,并且在嵌入某些内容时会有点担心。没有“退出监狱免费卡”以使用不适合标准关系方式的关系建模的东西。选择适合您需求的产品。