ARIES:一种支持细粒度锁定和部分回滚的事务恢复方法


内存与磁盘两难:

  • 内存速度很快,但不持久。磁盘很耐用,但速度很慢。
  • 我们想要既快速又耐用。
  • 我们可以在内存中执行并提交事务,以实现快速执行,但提交的事务也应该是持久的。将每个事务刷新到磁盘会在每次提交前增加长时间的 I/O 停顿。

因此,我们似乎陷入了进退两难的境地。

解决办法是什么?WAL:Write Ahead Log WAL
它有助于在工作记忆被擦除后恢复当前状态。

为了持久性,我们可能会强制将每个 WAL 更新(我们执行的操作的记录)写入磁盘。
这比将整个状态或物理块强制写入磁盘要小。

但是,刷新每次 WAL 更新(或至少在提交前刷新事务的 WAL 更新)会增加停滞时间。我们能做得更好吗?
怎样才能最有效(避免 I/O 停顿)地实现耐久性?

ARIES 可以帮我们实现这一目标。它不会留下任何问题。

在了解如何实现持久耐用性之前,我们需要进行一些额外的设置。
WAL 保存操作的逻辑记录,缓冲池保存页面/块(物理效果)。

ARIES
本文来自 IBM,1992 年。这是数据库领域的基础论文。ARIES 以高性能/非阻塞的方式实现了长时间运行的事务恢复。它比简单的(预写日志)基于 WAL 的每操作恢复更复杂,因为它需要保留 ACID 事务的原子性和持久性属性。

任何有价值的事务数据库(包括 PostGres、Oracle、MySQL)都基于 ARIES 原则实施恢复技术。

我们可以获得的最高性能是使用StealNo-Force策略。

  • Steal 说:事务可以从缓冲区中窃取页面来完成工作。这允许事务 C 的脏页在完成之前写入磁盘,以便为事务 B 提供工作空间。
  • No-Force 说:已完成事务的脏页可以在方便的时候刷新到磁盘。在我们提交事务之前,它们不必刷新到磁盘。

我们希望我们的 WAL 和恢复协议能够支持缓冲池的 "偷窃 "和 "不强制 "策略,以便在确保持久性的同时不影响性能。

考虑到 "Steal"和 "No-Force "限制,ARIES 做了额外的努力:它尝试让 WAL 刷新几乎同步进行,以避免 WAL 刷新造成任何停滞。

当然,为了保证正确性,在刷新 WAL 时需要有一点点依赖性/同步性。我们将在原则 1 下讨论这一点。


原则 1.
先写日志:对象的任何更改都会首先记录在日志中,日志必须在对象的更改写入磁盘之前写入稳定存储。

请注意,这并不意味着需要立即刷新 WAL 记录。这实际上给了我们很大的灵活性,让我们可以在大多数情况下以异步方式分批刷新 WAL 记录。唯一的例外情况是,缓冲池正在将 WAL 尚未刷新的脏页面写回磁盘。只有在这种情况下,我们才需要在将页面写回磁盘之前刷新 WAL。

这是最重要的原则。我就不详细介绍整个协议了。我将提及另外两个基本原则,并向你推荐两个相关的好资源。

原则 2.
在重做过程中重复历史:崩溃后重新启动时,ARIES 会回溯数据库在崩溃前的操作,并将系统恢复到崩溃前的准确状态。然后,它会撤销崩溃时仍在运行的事务,因为这些事务没有提交,需要回滚。  

原则 3.
记录撤消过程中的更改:对撤销事务时对数据库所做的更改进行记录,以确保在重复重启时不会重复此类操作。

ARIES 的正式建模
在我们了解原理/不变量之后,ARIES 并不是一个复杂的协议。但是,数据库恢复是一件棘手的事情,ARIES 协议不可避免地有很多细节。在实现中很容易出错,并且很难测试我们的实现是否正确进行恢复。

造成这种情况的原因有几个。恢复代码不会经常使用(希望您的数据库不会一直崩溃)。恢复的状态空间巨大,因此很难对事物进行建模。验证方法需要考虑崩溃的时间,并确保从任何不合时宜的崩溃中恢复。这是一个难题,直到最近我们才开始看到验证工作解决了这个问题。

不同层之间的接缝处也存在错误。FAST'18 的一篇论文表明,基于共识的存储在恢复过程中仍然存在许多错误。底层实现又如何呢?您确定fsync 刷新正确吗