MongoDB:解决竞争条件的策略

时间:2014-01-04 07:46:02

标签: php mongodb

我们需要在网站模型下存储多个Feed,如下所示:

{
  id: site_id
  name: site_name
  feeds: [
    {
      url: feed_url_1
      date: feed_update_date_1
    },
    {
      url: feed_url_2
      date: feed_update_date_2
    },
    ...
  ]
}

由于feeds是一个数组,我们可以使用$set$push$addToSet对其进行更新。

当我们的并发应用程序(队列)尝试更新同一站点模型时,可能会发生2种不同的竞争条件(写入偏斜)。

如果我们选择$set,并在客户端保护副本,那么如果2个队列正在写入同一站点,则可能会丢失一个源,但序列如下。

Given a wordpress site, extract 2 feeds (RSS and ATOM), dispatch to Q1 and Q2.
Q1: load existing feed, check RSS feed is new
Q2: load existing feed, check ATOM feed is new
Q1: $set feeds => [RSS]
Q2: $set feeds => [ATOM]

现在RSS Feed已丢失。

如果我们选择$push$addToSet,则可能会发生以下情况。

User A added a site, putting RSS feed to Q1
User B added the same site, putting the same RSS feed to Q2
Q1: load existing feed, check RSS feed is new
Q2: load existing feed, check RSS feed is new
Q1: $push RSS
Q2: $push RSS

现在RSS Feed已被复制

如果我们的数据模型只是{ url },那么$addToSet将防止重复投放。但不幸的是,情况并非如此,date属性可能会有所不同。因此$addToSet并不比$push安全得多。

我们已经想到了这个问题的一些可能的解决方法,但鉴于我们的时间紧迫,没有一个是好的。

  1. 将Feed从网站分离到自己的集合中,单独使用url进行保护,并相应地更改我们的模型和存储库。

  2. 首先将部分{ url }插入网站模型,然后使用其他信息更新它们,这会使$addToSet可用,但可能会破坏需要date的其他队列永远存在(需要测试)。

  3. 让竞争条件按原样发生,$push首先使用背景,使用后台队列检测重复并稍后将其删除。

  4. (如果upsert与位置查询一起工作,可能会有第4个解决方案,但据我所知MongoDB v2.4还没有它)

    所以我想知道是否有更好的选择来解决这种竞争条件。或者,如果有一些最佳实践。

2 个答案:

答案 0 :(得分:4)

你可能想看看tokumx,一个支持transactions的mongodb分支(除了一些其他有用的东西)

答案 1 :(得分:2)

您可以在更新选择器上使用gard:

alice(mongod-2.4.8) test> db.foo.save({_id: 12 })
Updated 1 new record(s) in 1ms
alice(mongod-2.4.8) test> db.foo.update({ _id: 12, "feeds.url" : {$ne: "baz"} }, 
    { $push :     { feeds : { url: "baz" } } } )
Updated 1 existing record(s) in 1ms
alice(mongod-2.4.8) test> db.foo.update({ _id: 12, "feeds.url" : {$ne: "baz"} },
    { $push : { feeds : { url: "baz" } } } )
Updated 0 record(s) in 1ms
alice(mongod-2.4.8) test> db.foo.find({_id: 12 })
{
    "_id": 12,
    "feeds": [
        {
            "url": "baz"
        }
    ]
}
Fetched 1 record(s) in 1ms -- Index[_id_]