【问题标题】:mongoDB optimistic concurrency control for update用于更新的 mongoDB 乐观并发控制
【发布时间】:2020-04-25 21:22:17
【问题描述】:

我正在为 MongoDB 的“更新”模拟 multiple concurrent request

事情是这样的,我在mongoDB中插入了一个数据amount=1000,每次触发api时,它都会更新amount += 50的数量并保存回数据库。基本上它是对单个文档的find and update 操作。

    err := globalDB.C("bank").Find(bson.M{"account": account}).One(&entry)

    if err != nil {
        panic(err)
    }

    wait := Random(1, 100)
    time.Sleep(time.Duration(wait) * time.Millisecond)

    //step 3: add current balance and update back to database
    entry.Amount = entry.Amount + 50.000
    err = globalDB.C("bank").UpdateId(entry.ID, &entry)

Here 是项目的源代码。

我正在使用 Vegeta 模拟请求:

如果我设置-rate=10(意思是每秒触发10次api,所以1000 + 50 * 10 = 1500),数据是正确的

echo "GET http://localhost:8000" | \
vegeta attack -rate=10 -connections=1 -duration=1s | \
tee results.bin | \
vegeta report

但是使用-rate=100(这意味着每秒触发 api 100 次,因此 1000 + 50 * 100 = 6000)会产生非常混乱的结果。

echo "GET http://localhost:8000" | \
vegeta attack -rate=100 -connections=1 -duration=1s | \
tee results.bin | \
vegeta report

简而言之,我想知道的是:我以为MongoDB正在使用optimistic concurrency control,这意味着如果有write conflict,它应该再次重试,所以延迟会上升,但数据应该保证是正确的。

为什么结果看起来在 MongoDB 中完全不能保证数据的正确性?

我知道你们中的一些人可能会注意到 4142 行的睡眠,但即使我将其注释掉,当我使用 -rate=500 进行测试时,结果仍然不正确。

任何线索为什么会发生这种情况?

【问题讨论】:

  • 哪些操作没有产生预期的结果?将那些从源中提取到问题正文中。

标签: mongodb optimistic-concurrency


【解决方案1】:

通常您应该将代码的相关部分提取到问题中。要求人们在你的 76 行程序中找到 5 个相关行是不体贴的。

您的测试正在执行并发的查找和修改操作。假设有两个并发进程 A 和 B,每个进程将账户余额增加 50。起始余额为 0。操作顺序可以是:

A: what is the current balance for account 1234?
B: what is the current balance for account 1234?
DB -> A: balance for account 1234 is 0
DB -> B: balance for account 1234 is 0
A: new balance is 0+50 = 50
A: set balance for account 1234 to 50
DB -> A: ok, new balance for account 1234 is 50
B: new balance is 0+50 = 50
B: set balance for account 1234 to 50
DB -> B: ok, new balance for account 1234 is 50

从数据库的角度来看,这里没有“写冲突”。您两次要求将给定帐户的余额设置为 50。

有不同的方法可以解决这个问题。一种是使用条件更新,使过程如下所示:

A: what is the current balance for account 1234?
B: what is the current balance for account 1234?
DB -> A: balance for account 1234 is 0
DB -> B: balance for account 1234 is 0
A: new balance is 0+50 = 50
A: if balance in account 1234 is 0, set balance to 50
DB -> A: ok, new balance for account 1234 is 50
B: new balance is 0+50 = 50
B: if balance in account 1234 is 0, set balance to 50
DB -> B: balance is not 0, no update was performed
B: err, let's start over
B: what is the current balance for account 1234?
DB -> B: balance for account 1234 is 50
B: new balance is 50+50 = 100
B: if balance in account 1234 is 50, set balance to 100
DB -> B: ok, new balance for account 1234 is 100

如您所见,数据库必须支持条件更新,应用程序必须处理并发更新的可能性并重试操作。

如果余额可以上下浮动,这不是编写借记和贷记系统的实用方法(但如果余额只能增加或只能减少,这实际上可以很好地工作)。在实际系统中,您将使用一个特殊字段,其目的是识别在应用程序检索某些数据时存在的文档的特定版本;更新的条件是文档的当前版本保持不变,并且每次更新都会增加版本。然后将检测到并发更新,因为版本号错误而不是内容字段。

有一些方法可以在数据库端产生“写入冲突”,例如使用 MongoDB 4.0+ 支持的事务。原则上,它的工作方式相同,但“版本”被称为“事务标识符”,它存储在不同的位置(不是内联在正在操作的文档中)。但原理是一样的。在这种情况下,数据库会通知您存在写入冲突,您仍需要重新发出操作。

更新:

我认为您还需要区分“乐观货币控制”作为一个概念、它的实施以及实施适用于什么。 https://docs.mongodb.com/manual/faq/concurrency/#how-granular-are-locks-in-mongodb 例如说:

对于大多数读写操作,WiredTiger 使用乐观并发控制。 WiredTiger 仅在全局、数据库和集合级别使用意图锁。当存储引擎检测到两个操作之间的冲突时,会引发写入冲突,导致 MongoDB 透明地重试该操作。

仔细阅读此声明,它适用于存储引擎级别的写操作。我想当 MongoDB 执行类似$set 或其他原子写入操作时,这将适用。但这不适用于您在示例中给出的应用程序级操作序列。

如果你用你最喜欢的关系 DBMS 尝试你的示例代码,我想你会发现它产生的结果与你在 MongoDB 中看到的结果大致相同,如果你围绕每个单独的读取和写入发出事务(这样平衡读取和写入在不同的事务中),出于同样的原因 - RDBMS 在事务的生命周期内锁定数据(或使用 MVCC 等技术),而不是跨事务。

类似地,如果您将同一账户上的余额读取和余额写入放入 MongoDB 中的事务中,您可能会发现当其他事务同时修改相关账户时,您会收到临时错误。

最后,MongoDB 为事务(带重试)实现的 API 描述为 here。如果您仔细查看它,您会发现它期望应用程序不仅重新发出事务提交命令,而且重复整个事务操作。这是因为通常情况下,如果存在“写入冲突”,则起始数据已更改,并且仅再次尝试最终写入是不够的 - 可能需要重做应用程序中的计算,甚至可能该过程的副作用发生变化结果。

【讨论】:

  • 感谢您的详细解释奥列格。但是,我对以下几点感到有些困惑。 1。据我所知,事务应该用于检测多文档的读写。在这种情况下,由于它是单个文件的读写。我认为事务不适用于此示例。然而,版本控制,我相信它会起作用。 2。那么这意味着 mongoDB 正在使用乐观的货币控制呢?看起来我们需要通过事务、版本控制或两阶段提交来处理来自应用程序端的所有写入冲突?在数据库中它本身没有做任何检查?
  • 我想我现在知道第二个的答案了,因为 mongoDB 没有在文档级别应用乐观并发控制。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-12
  • 1970-01-01
  • 1970-01-01
  • 2021-08-14
  • 1970-01-01
相关资源
最近更新 更多