CockroachDB 读取事务
CockroachDB read transactions
我一直在阅读 Google Spanner 和 CockroachDB 中实现的只读无锁事务。两者都声称通过使用系统时钟以无锁方式实现。在开始提问之前,这是我的理解(如果您了解两个系统中的机器或只了解 CockroachDB 中的机器,请跳过以下部分):
- Spanner 的方法更简单——在提交写入事务之前,Spanner 选择所有涉及的分片中的最大时间戳作为提交时间戳,添加一个等待,称为 commit wait,以从写入事务返回之前的最大时钟错误。这意味着所有因果关系事务(读取和写入)的时间戳值都将高于前一次写入的提交时间戳。对于读取事务,我们选择服务节点上的最新时间戳。例如,如果在时间戳 5 处提交了写入,并且最大时钟错误为 2,则未来的写入和只读事务的时间戳至少为 7。
- 另一方面,CockroachDB 做的事情更复杂。在写入时,它会在所有涉及的分片中选择最高的时间戳,但不会等待。在读取时,它分配一个初步的读取时间戳作为服务节点上的当前时间戳,然后通过读取所有分片并在任何分片上的任何键报告写入时间戳可能暗示写入是否因果关系的不确定性时重新启动读取事务来乐观地进行在读取事务之前。它假定写入时间戳小于读取事务时间戳的键要么出现在读取事务之前,要么与读取事务并发。不确定性机制在高于读取事务时间戳的时间戳上启动。例如,如果在时间戳 8 处提交了写入,并且为读取事务分配了时间戳 7,我们不确定该写入是在读取之前还是之后,因此我们以读取时间戳 8 重新启动读取事务。
相关来源 - https://www.cockroachlabs.com/blog/living-without-atomic-clocks/ and https://static.googleusercontent.com/media/research.google.com/en//archive/spanner-osdi2012.pdf
鉴于此实现,CockroachDB 是否保证以下两个事务不会违反可串行性?
- 一个用户阻止了另一个用户,然后 post 发送一条他们不希望被阻止的用户看到的消息作为一个写入事务。
- 被阻止的用户将他们的朋友列表和他们的 post 作为一个读取事务加载。
例如,假设好友列表和 post 存在于不同的分片上。并且发生以下排序(假设最大时钟误差为 2)
- 最初的 post 和好友列表是在时间戳 5 提交的。
- 读取事务从时间戳 7 开始,它读取好友列表,它认为该列表是在时间戳 5 提交的。
- 然后在 6 点提交用于阻止朋友和制作 post 的写入事务。
- 读取事务读取 posts,它认为这是在时间戳 6 提交的。
现在,事务违反了可串行化性,因为读取事务在同一事务中看到旧写入和新写入。
我错过了什么?
CockroachDB 使用一种叫做 timestamp cache 的机制来处理这个问题(这是一个不幸的名字;它不是一个缓存)。
在这个例子中,在第二步,当事务在时间戳 7 读取好友列表时,保存好友列表的分片记住它已经在 t=7(请求的时间戳读取事务,而不是现有数据的 last-modified 时间戳)并且它不再允许任何写入以较低的时间戳提交。
然后在第三步中,当写入事务尝试在 t=6 写入并提交时,会检测到此冲突,并且写入事务的时间戳会被推到 t=8 或更高。然后该事务必须 refresh its reads 以查看它是否可以在 t=8 时提交 as-is。否则,可能会返回错误,必须从头重试交易。
在第四步中,读取事务完成,看到了数据在 t=7 时存在的一致快照,而写入事务的两部分都在 t=8 时处于“未来”状态。
我一直在阅读 Google Spanner 和 CockroachDB 中实现的只读无锁事务。两者都声称通过使用系统时钟以无锁方式实现。在开始提问之前,这是我的理解(如果您了解两个系统中的机器或只了解 CockroachDB 中的机器,请跳过以下部分):
- Spanner 的方法更简单——在提交写入事务之前,Spanner 选择所有涉及的分片中的最大时间戳作为提交时间戳,添加一个等待,称为 commit wait,以从写入事务返回之前的最大时钟错误。这意味着所有因果关系事务(读取和写入)的时间戳值都将高于前一次写入的提交时间戳。对于读取事务,我们选择服务节点上的最新时间戳。例如,如果在时间戳 5 处提交了写入,并且最大时钟错误为 2,则未来的写入和只读事务的时间戳至少为 7。
- 另一方面,CockroachDB 做的事情更复杂。在写入时,它会在所有涉及的分片中选择最高的时间戳,但不会等待。在读取时,它分配一个初步的读取时间戳作为服务节点上的当前时间戳,然后通过读取所有分片并在任何分片上的任何键报告写入时间戳可能暗示写入是否因果关系的不确定性时重新启动读取事务来乐观地进行在读取事务之前。它假定写入时间戳小于读取事务时间戳的键要么出现在读取事务之前,要么与读取事务并发。不确定性机制在高于读取事务时间戳的时间戳上启动。例如,如果在时间戳 8 处提交了写入,并且为读取事务分配了时间戳 7,我们不确定该写入是在读取之前还是之后,因此我们以读取时间戳 8 重新启动读取事务。
相关来源 - https://www.cockroachlabs.com/blog/living-without-atomic-clocks/ and https://static.googleusercontent.com/media/research.google.com/en//archive/spanner-osdi2012.pdf
鉴于此实现,CockroachDB 是否保证以下两个事务不会违反可串行性?
- 一个用户阻止了另一个用户,然后 post 发送一条他们不希望被阻止的用户看到的消息作为一个写入事务。
- 被阻止的用户将他们的朋友列表和他们的 post 作为一个读取事务加载。
例如,假设好友列表和 post 存在于不同的分片上。并且发生以下排序(假设最大时钟误差为 2)
- 最初的 post 和好友列表是在时间戳 5 提交的。
- 读取事务从时间戳 7 开始,它读取好友列表,它认为该列表是在时间戳 5 提交的。
- 然后在 6 点提交用于阻止朋友和制作 post 的写入事务。
- 读取事务读取 posts,它认为这是在时间戳 6 提交的。
现在,事务违反了可串行化性,因为读取事务在同一事务中看到旧写入和新写入。
我错过了什么?
CockroachDB 使用一种叫做 timestamp cache 的机制来处理这个问题(这是一个不幸的名字;它不是一个缓存)。
在这个例子中,在第二步,当事务在时间戳 7 读取好友列表时,保存好友列表的分片记住它已经在 t=7(请求的时间戳读取事务,而不是现有数据的 last-modified 时间戳)并且它不再允许任何写入以较低的时间戳提交。
然后在第三步中,当写入事务尝试在 t=6 写入并提交时,会检测到此冲突,并且写入事务的时间戳会被推到 t=8 或更高。然后该事务必须 refresh its reads 以查看它是否可以在 t=8 时提交 as-is。否则,可能会返回错误,必须从头重试交易。
在第四步中,读取事务完成,看到了数据在 t=7 时存在的一致快照,而写入事务的两部分都在 t=8 时处于“未来”状态。