【问题标题】:MongoDB: strategies for resolving a race conditionMongoDB:解决竞争条件的策略
【发布时间】:2014-01-04 07:46:02
【问题描述】:

我们有一个场景,我们需要在一个站点模型下存储多个提要,如下所示:

{
  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 提要丢失了。

如果我们选择$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 提要已被复制

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

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

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

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

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

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

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

【问题讨论】:

  • 你很可能是用错了商店。对于基于文档的数据库,您必须将唯一性信息存储为值的组合名称的一部分,这样就不会发生重复。因此,在您的示例中,您为 RSS 保留一个子文档,为 ATOM 保留一个子文档。您必须跳出框框思考,将您认为是标准工作中的竞争条件转变为具有不一致且最终重复的数据。
  • @hakre thx,如果是这样,那么我们需要为每个提要 url 生成某种 slug,因为可以有多个 RSS/ATOM 提要。不幸的是,这确实需要对模型进行相当多的更改,但我同意这种方法是有意义的。
  • @hakre 实际上,如果您建议将 feeds 从数组转换为对象(或拆分为单独的属性),我想再次确认。这种方法需要放松我们的验证,它像传统的 RDBMS 验证一样通过列名来保护(另外我们可能有超过 1 个 ATOM 和 1 个 RSS 提要要处理)。否则我看不出我们如何才能避免使用数组写入偏斜。
  • 如果您保护传统 RDBMS 之类的东西,那么您使用的工具是错误的,Mongo 不是 RDBMS(也不是特别好的基于文档的数据库)。考虑适合这项工作的工具,否则你最终会遇到源源不断的问题,“没有特别针对你需要的详细程度得到回答”
  • @hakre 考虑到我们实际上将提要项目存储为文档,在一个单独的集合中,我认为这个工具非常适合我们的需求。在这种情况下,一个站点只能有几个提要,如果我们正确地阅读了 mongodb 文档,则嵌入是此类元数据的正确选择。唯一的问题是我们希望避免重复元数据。

标签: php mongodb


【解决方案1】:

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

【讨论】:

  • 由于 MongoDB 的全部目的是在分片分布式设置中使用,我认为使用 toku 事务有点违背了使用 MongoDB 的意义
【解决方案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_]

【讨论】:

  • 看到这个我就想:我们怎么没想到呢?这当然是一个有效的解决方案。
  • 这很容易是最好的答案,将在 mongodbs 环境中工作,一个分布式分片数据集
猜你喜欢
  • 2019-08-20
  • 1970-01-01
  • 1970-01-01
  • 2015-08-13
  • 2019-03-21
  • 2013-10-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多