如何在没有竞争条件的情况下使用mongodb实现有序数组?

时间:2013-04-09 10:21:12

标签: mongodb optimization

我是mongodb的新手,也许这是一个微不足道的问题。我有两个mongodb集合:userpost。用户可以创建和关注多个帖子,并按照上次修改日期列出帖子。特定帖子后可能会有大量用户,因此我不想在每个帖子文档中保留关注者列表。另一方面,一个用户可能不会关注超过几千个帖子,所以我决定在每个用户文档中保留关注帖子的目标列表。

为了能够快速列出给定用户最近修改的50个帖子,我选择保留last_updated_at字段以及帖子objectid。

post文档非常基础:

{
    "_id" : ObjectId("5163deebe4d809d55d27e847"),
    "title" : "All about music"
    "comments": [...]
    ...
}

user文档如下所示:

{
  "_id": ObjectId("5163deebe4d809d55d27e846"),
  "posts": [{
    "post": ObjectId("5163deebe4d809d55d27e847"),
    "last_updated_at": ISODate("2013-04-09T11:27:07.184Z")
  }, {
    "post": ObjectId("5163deebe4d809d55d27e847"),
    "last_updated_at": ISODate("2013-04-09T11:27:07.187Z")
  }]
  ...
}

当用户创建或关注帖子时,我可以$push将帖子的ObjectIdlast_updated_at添加到用户文档中posts列表的末尾。修改帖子时(例如,在帖子中添加评论时),我会在所有关注者的用户文档中更新该帖子的last_updated_at字段。那很重,但我不知道如何避免它。

当我想获取用户最近更新的50个帖子的列表时,我很遗憾地需要获取所有后续帖子的列表,然后在内存中按last_updated_at排序,然后只保留前50个帖子

因此,我尝试更改实施,以便在修改帖子时对列表进行重新排序:我$push将其添加到列表的末尾,并$pull将其从任何位置开始。由于这是一个两步程序,因此存在竞争条件,我可能会在列表中获得两倍相同的帖子。有没有更好的方法来维护mongodb中的排序数组?

1 个答案:

答案 0 :(得分:9)

数据模型调整

由于您可能经常更新给定用户的最新帖子,因此您可能希望避免不必要地重写数据以维护已排序数组的开销。

更好的考虑方法是展平数据模型并使用单独的集合而不是有序数组:

  • 使用更新的帖子流创建单独的集合:(userID, postID, lastUpdated)
  • 当帖子更新后,您可以使用multi:trueupsert:true选项以及$set last_updated_at为新值执行简单的update()
  • 要检索给定用户ID的最近50个更新帖子,您可以使用排序和限制选项执行正常find()
  • 自动清理“旧”文档,您甚至可以为此集合设置TTL expiry,以便在一定天数后从活动流中删除更新

推动固定尺寸&在MongoDB 2.4

中排序的数组

如果你想维护有序数组,MongoDB 2.4增加了两个与这个用例相关的有用功能:

  • 能够推送到固定大小的数组
  • 能够推送到按嵌入文档字段排序的数组

因此,您可以实现推送到按上次更新日期降序排序的50个项目的固定大小数组的结果:

db.user.update(
    // Criteria
    { _id: ObjectId("5163deebe4d809d55d27e846") },

    // Update
    { $push: {
        posts: {
            // Push one or more updates onto the posts array
            $each: [
                {
                    "post": ObjectId("5163deebe4d809d55d27e847"),
                    "last_updated_at": ISODate()
                }
            ],

            // Slice to max of 50 items
            $slice:-50,

            // Sorted by last_updated_at desc
            $sort: {'last_updated_at': -1}
        }
    }}
)

$push将按排序顺序更新列表,$slice将列表修剪为前50个项目。由于帖子不是唯一的,因此您仍然需要首先$pull列表中的原始文件,例如:

db.user.update(
    // Criteria
    { _id: ObjectId("5163deebe4d809d55d27e846") },

    // Update
    { 
        $pull: {
            posts: { post: ObjectId("5163deebe4d809d55d27e847") }
        }
    }
)

这种方法的一个好处是数组操作正在服务器上完成,但与在应用程序中对数组进行排序一样,您可能仍然需要更新文档。