在BASE风格的数据库中的”Optimistic Locking”(乐观锁定)。

huangapple go评论68阅读模式
英文:

Optimistic Locking in BASE style databases

问题

BASE风格的数据库是软状态和最终一致性的。我明白不同的数据库管理系统会有所不同,它们的配置会产生很大的影响。但让我们来想象一下:

假设我有一个包含2个节点的NoSQL集群。它是最终一致性的。当我写入Node1时,经过一段时间后它会被复制到Node2。让我通过比较版本来实现一种乐观锁定机制。因此,当我尝试写入Node1时,如果我的版本落后于数据库中的版本,写操作将不会成功。但是当有两个线程(或进程)尝试更新同一行时,可能会出现类似以下情况:

  1. 线程1从Node1读取版本为0的数据
  2. 线程2从Node2读取版本为0的数据
  3. 线程1在Node1上将数据更新为版本1
  4. 线程2尝试在Node2上更新版本为1的数据,它可能会成功,因为写操作可能尚未被复制。

我是否漏掉了什么?一些BASE风格的数据库系统是如何解决这个问题的?你能给我一些关于哪些数据库系统如何解决这个问题的实际例子吗?

英文:

BASE style databases are Soft State and Eventually Consistent. I'm aware that different database management systems vary and their configurations make a huge difference. But let's imagine this:

Let's say I have a NoSQL cluster with 2 nodes. It's eventually consistent. When I write to Node1, it's replicated to Node2 after some time. Let me implement an Optimistic Locking mechanism by comparing the versions. So, when I try to write to Node1, write operation will not be successful if my version is behind the version in the database. But when there are two threads (or process) try to update the same row, something like this can happen:

  1. thread1 reads the data with version=0 from Node1
  2. thread2 reads the data with version=0 from Node2
  3. thread1 updates the data with version=1 on Node1
  4. thread2 tries to update the data with version=1 on Node2 and it may success because write operation may not be replicated yet.

Am I missing something? How do some BASE style database systems solve this? Can you give me solid examples about which database system solves this problem how?

答案1

得分: 2

这可能不是讨论这种问题的最佳论坛,因为它可能涉及讨论和来回交流。因此,您可能希望查看Couchbase Discord

但是,我可以告诉您,对于Couchbase(可能符合您所描述的“BASE”标准,这是另一个话题),实现锁定的方法称为“比较和交换”(也称为CAS)。从文档中

CAS,或Compare And Swap,是一种乐观锁定的形式。Couchbase中的每个文档都有一个CAS值,并且在每次变异时都会更改。当您获取文档时,还会获取文档的CAS,然后在写入文档时,将相同的CAS发送回来。如果另一个线程或程序在此期间修改了该文档,Couchbase服务器可以检测到您提供了一个已过时的CAS,并返回错误。这提供了廉价且安全的并发性。

关于如何在SDK级别实现这一点的详细信息可以在文档中找到(例如,Java SDK并发文档变异文档)。

作为数据库的最终用户,您只需要担心保持CAS值,因为它是乐观锁(也是Couchbase中的一种选择的悲观锁)的关键。为了进行乐观锁的CAS值比较,通常需要重试循环,以防文档受争用。 (如果文档经常受到严重争用,请考虑使用悲观锁)。

如果您想更深入地了解实际的CAS实现(即,您正在编写自己的数据库,我猜),我再次建议参考Couchbase Discord,因为核心工程师经常在那里聚集。

英文:

I'm not sure this is the best forum for this kind of question, because it might involve discussion and back-and-forth. So you may want to check out the Couchbase Discord.

However, I can tell you that for Couchbase (which might meet the "BASE" criteria as you have described it, that's a whole other conversation), the method by which locking is implemented is called "Compare-and-Swap" (aka CAS). From the documentation:

> CAS, or Compare And Swap, is a form of optimistic locking. Every
> document in Couchbase has a CAS value, and it’s changed on every
> mutation. When you get a document you also get the document's CAS, and
> then when it’s time to write the document, you send the same CAS back.
> If another thread or program has modified that document in the
> meantime, the Couchbase Server can detect you've provided a
> now-outdated CAS, and return an error. This provides cheap, safe
> concurrency.

And there's more detail on how this works at an SDK level in the documentation (for instance, the Java SDK Concurrent Document Mutations docs).

As a end user of the database, all you need to worry about is holding on to the CAS value, as it's the key to the optimistic lock (and pessimistic lock, which is also an option in Couchbase). Comparing the CAS values for optimistic locking will often require a retry loop just in case the document is under contention. (And if it's often under heavy contention, consider a pessimistic lock instead).

If you want to get deeper into the actual CAS implementation (i.e. you're writing your own database, I guess), I would again point to the Couchbase Discord, as the core engineers often hang out there.

答案2

得分: 2

Matthew Groves的答案完全正确。我只想补充一些背景信息。

在Couchbase中,键/值写入对所有人都是立即可见的。

这是因为每个文档都根据其文档ID分配给一个分区。每个分区都有一个“活动”版本,以及分布在集群中的“副本”。Couchbase客户端SDK只与“活动”分区交互,除非您明确告诉它要从副本读取。

  1. 线程1从Node1读取版本为0的数据
  2. 线程2从Node2读取版本为0的数据

这意味着在您的示例中,线程1和线程2将与同一个节点交互(只要它们访问同一文档)。唯一的例外是在集群重新平衡期间,副本可能会被提升为“活动”。不过,对于乐观锁定来说,这没问题,因为副本文档具有与活动分区文档相同的比较和交换值(只要它确实是相同版本,而不是过时的)。

请查看了解vBuckets以获取更多信息。

还可以参考Couchbase论坛上的这篇帖子:如何实现强一致性

英文:

Matthew Groves' answer is spot on. I'll just add a bit more context.

With Couchbase, Key/Value writes are immediately visible to everyone.

That's because each document is assigned to a partition based on its document ID. There's an "active" version of the partition, as well as "replicas" distributed across the cluster. A Couchbase client SDK only talks to the active partition, unless you explicitly tell it to read from a replica.

> 1. thread1 reads the data with version=0 from Node1
> 2. thread2 reads the data with version=0 from Node2

This means that in your example, thread1 and thread2 will talk to the same node (as long as they're accessing the same document). The exception is that during a cluster rebalance, replicas might be promoted to "active". That's fine for optimistic locking, though, since the replica document has the same Compare-And-Swap value as the document from the active partition (as long as it's really the same version, and not stale).

See Understanding vBuckets for more info.

And also this post from the Couchbase forum: https://www.couchbase.com/forums/t/how-can-i-archive-strong-consistency/17530

huangapple
  • 本文由 发表于 2023年8月5日 08:12:33
  • 转载请务必保留本文链接:https://go.coder-hub.com/76839678.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定