因果一致性和读写问题

使用 MongoDB 的因果一致的 Client 会话,读写问题的不同组合提供了不同的因果一致性保证。如果定义因果一致性以表示耐久性,则下表列出了各种组合提供的特定保证:

Read ConcernWrite Concern阅读自己的文章Monotonic readsMonotonic writes写跟读
"majority""majority"
"majority"{ w: 1 }
"local"{ w: 1 }
"local""majority"

如果因果一致性表示持久性,那么从表中可以看出,只有具有"majority"读关注点的读取操作和具有"majority"写入关注点的写入操作才能保证所有四个因果一致性保证。也就是说,因果一致的 Client 会话仅能保证以下情况的因果一致性:

  • 具有"majority"阅读关注的阅读操作;即读取操作,该操作返回大多数副本集成员已确认且持久的数据。

  • "majority"写关注的写操作;即要求确认该操作已应用于大多数副本集投票成员的写入操作。

如果因果一致性并不意味着持久性(即,写操作可能会回滚),则具有{ w: 1 }写入关注点的写操作也可以提供因果一致性。

Note

在某些情况下(但不一定在所有情况下),读和写关注点的其他组合也可以满足所有四个因果一致性保证。

读关注点"majority"和写关注点"majority"确保四个因果一致性保证即使在情况(例如带有网络分区的情况)中也是如此,因为副本集中的两个成员暂时地认为它们是主要的。尽管两个主数据库都可以完成{ w: 1 }写关注,但只有一个主数据库能够完成"majority"写关注。

例如,考虑网络分区划分五个成员副本集的情况:

网络分区:一侧选择了新的主节点,但旧的主节点尚未卸任。

With the above partition

  • 具有"majority"写关注的写操作可以在P新完成,但是不能在P旧完成。

  • 具有{ w: 1 }写关注的写可以在P旧或P新上完成。但是,一旦P old 的写入(以及复制到S1 的写入)回滚,则这些成员恢复与副本集其余部分的通信。

  • 成功对P new 进行"majority"写关注后,与"majority"读关注因果一致的读操作可以观察到P new,S 2 和S 3 上的写操作。一旦读入,读操作也可以观察到P old 和S1 上的写操作。与副本集的其余部分进行通信,并与副本集的其他成员进行同步。在分区期间对P old 和/或复制到S1 的所有写操作都会回滚。

Scenarios

为了说明读写关注点要求,在以下情况下,Client 端向 Client 端发出了一系列操作,并对副本集进行了读写关注点的各种组合:

阅读关注“多数”并写关注“多数”

在因果一致会话中使用读关注点"majority"和写关注点"majority"提供了以下因果一致性保证:

✅自己读✅单调读 read 单调写✅写跟随读

方案 1(读取关注点“多数”和写入关注点“多数”)

在具有两个主键的过渡期间,由于只有P new 可以满足{ w: "majority" }写问题,因此 Client 端会话可以成功发出以下操作序列:

SequenceExample
1.将关注点"majority"写为 1 到P

2.读为 1,读意为"majority"S 2
3.写 2,写关注点"majority"P
4.以关注点"majority"S 3 读取 2
对于项目A,将qty更新为50
阅读项目A
对于qty小于或等于50的项目,
restock更新为true
阅读项目A

使用读关注多数和写关注多数的具有两个 Primitives 的数据状态

读自己写的东西读取 1 从S 2 读取反映写入 1 之后状态的数据。


读取 2 从S 1 读取数据,该数据反映了 Write1 1 之后是 Write 2 之后的状态。
|✅ 单音符号读取 | Read2 从S 3 读取反映 Read1 之后状态的数据。
|✅ 单调写入 | Write2 更新P new 上的数据,以反映 Write1 之后的状态。
|✅ Writes 跟着读取 | Write2 更新Pnew 上的数据,该数据反映了 Read1 之后的数据状态(即,较早的状态反映了 Read1 读取的数据)。

方案 2(读取关注点“多数”和写入关注点“多数”)

考虑一个替代序列,其中具有读关注"majority"的 Read1 路由到S 1:

SequenceExample
1.将关注点"majority"写为 1 到P

2.阅读关注度为"majority"S 1 的 1
3.写 2,写关注点"majority"P
4.以关注点"majority"S 3 读取 2
对于项目A,将qty更新为50
阅读项目A
对于qty小于或等于50的项目,
restock更新为true
阅读项目A

按照这种 Sequences,直到多数提交点在P old 上前进,Read1 才返回。直到P old 和S1 可以与副本集的其余部分通信时,才会发生这种情况。那时P old 已下台(如果尚未退出),并且两个成员与副本集的其他成员同步(包括 Write1)。

读自己写的东西读 1 反映了写 1 1 之后的数据状态,尽管在网络分区已修复并且该成员已与副本集的其他成员同步之后。


读取 2 从S 3 读取数据,该数据反映了 Write1 1 之后是 Write 2 之后的状态。
|✅ 单声道读取 | Read2 从S 3 读取反映 Read1 之后状态的数据(即,较早的状态反映在 Read1 读取的数据中)。
|✅ 单调写入 | Write2 更新P new 上的数据,以反映 Write1 之后的状态。
|✅ Writes 跟着读取 | Write2 更新Pnew 上的数据,该数据反映了 Read1 之后的数据状态(即,较早的状态反映了 Read1 读取的数据)。

阅读关注“多数”并发表关注\ {w: 1}

在因果一致性会话中使用读关注点"majority"和写关注点{ w: 1 }提供以下因果一致性保证如果因果一致性暗示持久性

❌自己读✅单调读 read 单调写✅写跟随读

如果因果一致性并不意味着耐用性

✅自己读✅单调读 read 单调写✅写跟随读

方案 3(“关注多数”和“关注{w: 1}”)

在具有两个主键的过渡期内,由于P old 和P new 都可以满足{ w: 1 }写问题,因此 Client 端会话可以成功发出以下操作序列,但不能因果一致 如果因果一致性暗示持久性

SequenceExample
1.写入关注度为{ w: 1 }P老的 1

2.读为 1,读意为"majority"S 2
3.写 2,写关注点{ w: 1 }P
4.以关注点"majority"S 3 读取 2
对于项目A,将qty更新为50
阅读项目A
对于qty小于或等于50的项目,
restock更新为true
阅读项目A

使用读关注多数和写关注 1 的具有两个主数据的数据状态

按照这个 Sequences

  • 直到多数提交点在 Write1 的时间之后P new 前进之前,Read1 才返回。

  • 直到多数提交点在 Write2 之前P new 前进之前,Read2 才能返回。

  • 网络分区修复后,Write1 将回滚。

如果因果一致性意味着持久性

读自己写的东西Read1 从S 2 读取未反映 Write1 之后状态的数据。
单调发音Read2 从S 3 读取数据,该数据反映了 Read1 之后的状态(即,较早的状态反映在 Read1 读取的数据中)。
单调写作Write2 更新P new 上的数据,这些数据不反映 Write1 之后的状态。
读后随笔写Write2 更新P new 上的数据,该数据反映了 Read1 之后的状态(即,较早的状态反映了 Read1 读取的数据)。

如果因果一致性并不意味着耐用性

读自己写的东西Read1 从S读取数据。2 返回反映与 Write1 等效的状态的数据,然后回退 Write1.
单调发音Read2 从S 3 读取数据,该数据反映了 Read1 之后的状态(即,较早的状态反映在 Read1 读取的数据中)。
单调写作Write2 更新P new 上的数据,这等效于 Write1 之后回退 Write1 之后的数据。
读后随笔写Write2 更新了P new 上的数据,该数据反映了 Read1 之后的状态(即,其较早的状态反映了 Read1 读取的数据)。

方案 4(读取关注“多数”并关注{w: 1})

考虑一个替代序列,其中具有读关注"majority"的 Read1 路由到S 1:

SequenceExample
1.写入关注度为{ w: 1 }P老的 1

2.阅读关注度为"majority"S 1 的 1
3.写 2,写关注点{ w: 1 }P
4.以关注点"majority"S 3 读取 2
对于项目A,将qty更新为50
阅读项目A
对于qty小于或等于50的项目,
restock更新为true
阅读项目A

按此 Sequences:

  • 在多数提交点在S 1 上前进之前,Read1 将不会返回。直到P old 和S1 可以与副本集的其余部分通信时,才会发生 Read1.那时,Pold 已降级(如果尚未降级),Write1 从Pold 和S1 回滚,并且两个成员与副本集的其他成员同步。

如果因果一致性意味着持久性

读自己写的东西Read1 读取的数据不能反映已回退的 Write1 的结果。
单调发音Read2 从S 3 读取数据,该数据反映了 Read1 之后的状态(即,其较早的状态反映了 Read1 读取的数据)。
单调写作Write2 更新P new 上的数据,该数据不反映 Write1 之后的状态,该状态早于 Write2 但已回滚。
读后随笔写Write2 更新了P new 上的数据,该数据反映了 Read1 之后的状态(即,其较早的状态反映了 Read1 读取的数据)。

如果因果一致性并不意味着耐用性

读自己写的东西Read1 返回的数据反映了 Write1 的最终结果,因为 Write1 最终会回滚。
单调发音Read2 从S 3 读取数据,该数据反映了 Read1 之后的状态(即,较早的状态反映了 Read1 读取的数据)。
单调写作Write2 更新P new 上的数据,这等效于 Write1 之后回退 Write1 之后的数据。
读后随笔写Write2 更新P new 上的数据,该数据反映了 Read1 之后的状态(即,较早的状态反映了 Read1 读取的数据)。

读取关注内容“本地”并写入关注内容\ {w: 1}

在因果一致的会话中使用读关注点"local"和写关注点{ w: 1 }不能保证因果一致性。

❌自己读❌单调读 read 单调写❌写跟随读

在某些情况下(但不一定在所有情况下),此组合可以满足所有四个因果一致性保证。

方案 5(“本地”关注问题和“ {w: 1}”关注问题)

在此过渡期间,由于P old 和P new 都可以满足{ w: 1 }写问题的写操作,因此 Client 机会话可以成功发出以下操作序列,但因果关系不一致:

SequenceExample
1.写入关注度为{ w: 1 }P老的 1

2.阅读关注度为"local"S 1 的 1
3.写 2,写关注点{ w: 1 }P
4.以关注点"local"S 3 读取 2
对于项目A,将qty更新为50
阅读项目A
对于qty小于或等于50的项目,
restock更新为true
阅读项目A

使用读关注本地和写关注 1 的具有两个主数据的数据状态

❌自己写Read2 从S 3 读取数据,该数据仅反映 Write2 之后的状态,而不反映 Write1 之后是 Write2 的状态。
❌单调读Read2 从S 3 读取数据,该数据不反映 Read1 之后的状态(即,较早的状态不反映 Read1 读取的数据)。
❌单调写Write2 更新P new 上的数据,这些数据不反映 Write1 之后的状态。
❌写跟随读Write2 更新Pnew 上的数据,该数据不反映 Read1 之后的状态(即,较早的状态不反映 Read1 读取的数据)。

读取关注点“本地”并写入关注点“多数”

在因果一致会话中使用读关注点"local"和写关注点"majority"提供了以下因果一致性保证:

❌自己读❌单调读 read 单调写❌写跟随读

在某些情况下(但不一定在所有情况下),此组合可以满足所有四个因果一致性保证。

方案 6(“关注本地”和“关注多数”)

在此过渡期间,因为只有P new 可以满足{ w: "majority" }写问题,所以 Client 端会话可以成功发出以下操作序列,但因果关系不一致:

SequenceExample
1.将关注点"majority"写为 1 到P

2.阅读关注度为"local"S 1 的 1
3.写 2,写关注点"majority"P
4.以关注点"local"S 3 读取 2
对于项目A,将qty更新为50
阅读项目A
对于qty小于或等于50的项目,
restock更新为true
阅读项目A

使用读关注本地和写关注多数的两个主数据的状态

❌阅读自己的文章。Read1 从S 1 读取未反映 Write11 之后状态的数据。
❌单调读。Read2 从S 3 读取数据,该数据不反映 Read1 之后的状态(即,较早的状态不反映 Read1 读取的数据)。
✅单调写Write2 更新P new 上的数据,该数据反映 Write1 之后的状态。
❌写跟读。Write2 更新Pnew 上的数据,该数据不反映 Read1 之后的状态(即,较早的状态不反映 Read1 读取的数据)。