Concurrency 蟑螂读取事务

Concurrency 蟑螂读取事务,concurrency,distributed-system,distributed-transactions,google-cloud-spanner,cockroachdb,Concurrency,Distributed System,Distributed Transactions,Google Cloud Spanner,Cockroachdb,我一直在读关于GooglePanner和CockroachDB中实现的只读无锁事务的文章。两者都声称通过使用系统时钟以无锁方式实现。在回答这个问题之前,我的理解如下(如果您知道两个系统中的机械装置或蟑螂B中的机械装置,请跳过以下部分): 扳手的方法更简单——在提交写事务之前,扳手选择所有相关碎片的最大时间戳作为提交时间戳,并在从写事务返回之前添加一个称为提交等待的等待,以等待最大时钟错误。这意味着所有因果相关事务(读和写)的时间戳值将高于上一次写入的提交时间戳。对于读取事务,我们选择服务节点上

我一直在读关于GooglePanner和CockroachDB中实现的只读无锁事务的文章。两者都声称通过使用系统时钟以无锁方式实现。在回答这个问题之前,我的理解如下(如果您知道两个系统中的机械装置或蟑螂B中的机械装置,请跳过以下部分):

  • 扳手的方法更简单——在提交写事务之前,扳手选择所有相关碎片的最大时间戳作为提交时间戳,并在从写事务返回之前添加一个称为提交等待的等待,以等待最大时钟错误。这意味着所有因果相关事务(读和写)的时间戳值将高于上一次写入的提交时间戳。对于读取事务,我们选择服务节点上的最新时间戳。例如,如果在时间戳5提交了一个写操作,并且最大时钟错误为2,那么将来的只写和只读事务的时间戳至少为7
  • 另一方面,蟑螂B的作用更为复杂。在写入时,它在所有相关碎片中选择最高的时间戳,但不等待。在读取时,它将一个初步读取时间戳指定为服务节点上的当前时间戳,然后通过读取所有碎片并重新启动读取事务(如果任何碎片上的任何键报告一个写入时间戳,该写入时间戳可能暗示写入是否因果地先于读取事务的不确定性)。它假设写时间戳小于读事务时间戳的键出现在读事务之前,或者与读事务并发。不确定性机制在高于读取事务时间戳的时间戳上起作用。例如,如果在时间戳8提交了一个写操作,并且为一个读事务分配了时间戳7,那么我们不确定该写操作是在读操作之前还是之后进行的,因此我们使用读操作时间戳8重新启动读事务
相关来源——和


鉴于此实现,CockroachDB是否保证以下两个事务不会出现违反序列化性的情况

  • 用户阻止另一个用户,然后发布一条消息,不希望被阻止的用户将其视为一个写事务
  • 被阻止的用户将其好友列表和帖子作为一个读取事务加载
  • 作为一个例子,考虑好友列表和帖子以不同的碎片存在。并且发生以下顺序(假设最大时钟误差为2)

  • 初始帖子和好友列表在时间戳5提交
  • 读事务从时间戳7开始,它读取朋友列表,它认为朋友列表在时间戳5提交
  • 然后,用于阻止好友并发布帖子的写入事务在6时提交
  • read事务读取POST,它认为POST在时间戳6提交
  • 现在,事务违反了可序列化性,因为读事务在同一事务中看到了旧的写操作和新的写操作


    我遗漏了什么?

    蟑螂数据库使用一种称为的机制来处理这个问题(这是一个不幸的名称;它不是一个很好的缓存)

    在该示例中,在步骤2中,当事务在时间戳7处读取朋友列表时,保存朋友列表的碎片记住它已在t=7处为该数据提供读取服务(读取事务请求的时间戳,而不是存在的数据的最后修改的时间戳)而且它不再允许任何具有较低时间戳的写入提交

    然后在步骤3中,当写入事务尝试在t=6时写入和提交时,检测到该冲突,并且写入事务的时间戳被推送到t=8或更高。然后,该事务必须查看它是否可以在t=8时按原样提交。否则,可能会返回错误,必须从头开始重试事务


    在第四步中,读取事务完成,看到数据在t=7时的一致快照,而写入事务的两个部分在t=8时都是“将来”的。

    谢谢!很高兴听到有人直接研究蟑螂:)关于答案,我有两个后续问题:1)为什么时间戳缓存是缓存?如果说进程在服务读操作之后,但在写操作之前重新启动,那么这不存在违反序列化性的风险吗?2) 我假设您提到的读取刷新是针对RW或W事务的(因为读取事务描述在链接的CockroachDB博客文章中没有涉及第二次类似seqlock的扫描),而且Panner paper声称它使用2PL进行读写事务。鉴于此,读取刷新的用途是什么。当进程重新启动时,时间戳缓存将初始化为当前时间(加上最大时钟偏移)。这会导致一些时间戳推送和事务重新启动,如果进程没有失败,这些都是不必要的,但它保留了可序列化性。时间戳缓存可以像这样擦除和重置,这就是它成为“缓存”的原因。2.读刷新用于读/写事务。只写事务可以跳过读取刷新步骤;他们总是可以在新的更高的时间戳上提交。我明白了,这很聪明!这避免了通过paxos日志(IIUC)推送时间戳缓存。谢谢我不完全理解为什么需要读取刷新-rw事务在提交点(即2PL)之前不持有读取项的读取锁吗?对于没有选择更新的读取,时间戳缓存项实际上是“读取锁”。因此,密钥被锁定到某个时间戳,但允许写入高于该时间戳的内容。如果事务时间戳被推送,我们必须更新那些时间戳缓存条目。(SELECT FOR UPDATE不同;请参见此)在实践中,悲观锁定似乎非常重要