Aurora
原文:https://xie.infoq.cn/article/09849d56c3b18064af6c7f857
搬运用于参考学习
ABSTRACT
Amazon Aurora 是一个关系型数据库服务,其作为 Amazon Web Services(AWS)的一部分为 OLTP 业务提供服务。本文描述了 Aurora 的体系结构以及设计该结构时的一些思考。我们认为高吞吐量数据处理的核心问题已经从计算和存储转移到了网络。为了解决这个问题,Aurora 提出了一种新颖的体系结构,它将 REDO 日志的处理移到了一个专门为 Aurora 定制的多租户可扩展存储服务上。首先,我们介绍了 Aurora 的实现策略,包括减少网络流量、快速故障恢复、故障时无损切换到备机、以及容错且自愈的存储。然后,我们介绍了 Aurora 的高效异步策略,它能够在大量的存储节点上实现可持久状态的一致性,避免了昂贵且繁杂的恢复协议。最后,在 Aurora 在生产环境运行了 18 个月之后,我们分享了从客户那里学习到的经验教训:客户期望的现代云服务中的数据库应该是什么样的。
1. 引言
IT 业务正逐渐转移到公有云提供商。这个产业过渡的重要原因包括以下两个方面,其一是能够灵活地按需配置容量,其次是企业可以将配置的容量作为运营费用(OPEX)来支付,而非资本支出(CAPEX)。许多 IT 业务都需要一个关系型 OLTP 数据库,提供与本地数据库相同或更高级的功能对于支持这种长期过渡至关重要。
在现代分布式云服务中,弹性和可扩展性常常通过将计算与存储解耦,并在多个节点间进行复制存储来实现。通过这种方式,我们可以处理诸如替换异常或不可达的主机、增加备机、故障主机切换到备机、增删数据库实例等等操作。
在此背景下,传统数据库系统面临的 I/O 瓶颈正在发生变化。由于 I/O 操作可以分布在多租户机群的多个节点的多个磁盘上,因此单个磁盘和节点不再是热点。取而代之的是,瓶颈转移到了请求 I/O 操作的数据库层和执行这些 I/O 操作的存储层之间的网络上。除了每秒数据包数(PPS)和带宽瓶颈之外,由于数据库为了保证性能会并行地将数据写入存储层,因此这里会产生流量放大的问题。异常存储节点、磁盘或者网络路径的性能都会影响响应时间。
尽管数据库中的许多操作可以相互交叉,但是仍然存在一些场景需要同步操作。这就导致了暂停和上下文切换。其中一个场景是数据库的缓冲区缓存未命中而导致的磁盘读。读线程在读取完成前无法进行下一步操作。缓存未命中可能还会导致额外的处罚:清除缓存中的脏页以容纳新页。将诸如 Checkpoint 和脏页写入这类操作通过后台处理可以减少上述损失的发生,但仍然可能会导致暂停、上下文切换和资源争用。
事务提交是另一种影响性能的干扰项,在提交一个事务时出现的阻塞会阻碍其他事务的处理。在云分布式系统中,使用诸如 2 阶段提交(2PC)之类的多阶段同步协议处理事务提交具有较大的挑战。(比如说,)这些协议对于故障的容忍度较低,而在高度扩展的分布式系统中会有持续的软硬故障。并且由于分布式系统的节点分布在多个数据中心,有较大的处理时延。
本文提出了一个新型的数据库服务:Amazon Aurora,它通过在分布式云环境中更积极的利用 REDO 日志来解决上述问题。我们使用一种新颖的面向服务的体系结构(见图 1),使用多租户可扩展的存储服务,来抽象虚拟的分段 REDO 日志并松散的与一组数据库实例连接在一起。尽管每个实例仍然包含传统(数据库)内核的大部分组件(查询处理器、事务、锁、缓冲区缓存、访问方式以及撤销管理),但是一些功能(REDO 日志、持久化存储、故障恢复、备份以及恢复数据)被迁移到了存储服务中进行。
与传统方法相比,我们的体系结构具有三个重要的优势。首先,通过将存储作为一个跨数据中心且具有容错和自愈能力的服务进行构建,我们可以保护数据库免受网络和存储层的性能波动以及短期或长期故障的影响。我们观察到,一个持久化的故障可以建模为一个持久的可用性事件,而一个可用性事件可以建模为一个持久的性能抖动。一个设计良好的系统可以无差别的处理这些事件。其次,通过仅将 REDO 日志写入存储,我们将网络 IOPS 降低了一个数量级。一旦消除了网络 IOPS 的瓶颈,我们就能够更进一步地优化其他争用点,相比于我们刚开始时使用的基础 MySQL 源码而言,目前吞吐量已经有了显著的提升。第三,我们将一些最为复杂和关键的功能(备份和 REDO 恢复)进行了转移,原先是数据库引擎中的一次性昂贵的操作,而现在转移到一个大型分布式系统中进行连续异步操作。这种方式可以在不进行 Checkpoint 的情况下实现几乎即时的崩溃恢复,以及不影响前台处理的低成本的备份。
在本文中,我们首先介绍了以下三个贡献:
-
如何在云规模上实现可持久性,以及如何设计 Quorum 系统来应对关联故障。(第二节)
-
如何将传统数据库的下层部分转移到存储层实现智能存储。(第三节)
-
如何在分布式存储中移除多阶段同步、崩溃恢复以及 Checkpoint。(第四节)
然后在第五节中我们将展示如何将上述三个想法整合在一起,从而设计了 Aurora 的整体体系结构,然后在第六节中,对性能结果进行了回顾,并在第七节中介绍了我们从中吸取到的经验和教训。最后,我们在第八节中简洁地介绍了相关工作,并在第九节中进行了总结。
2. 大规模系统中的可持久性
如果一个数据库系统没有其他任务,那么它必须满足以下约定:一旦写入,就可以被读取。但是并非所有系统都满足(以上约定)。在本节中,我们将讨论所采用的 Quorum 模型的依据,为什么要分段存储以及如何将两者结合在一起,不仅满足可持久性、高可用性以及减少抖动,同时还能解决在大规模系统中管理存储层存在的运维问题。
2.1 复制以及关联故障
实例的生命周期与存储的生命周期不是强关联的。当实例挂掉了,客户可以将其关闭,也可以根据负载的大小决定上线或者下线。基于以上原因,我们将存储层和计算层进行了解耦。
一旦将存储层和计算层进行解耦,那些存储节点和磁盘也可能会发生故障。因此,必须通过某种方式将其进行复制以提供故障恢复能力。在一个大规模的云环境中,长期存在着节点、磁盘和网络路径故障的低频背景噪音。每个失败可能会持续不同的时间,带来不同的影响范围。例如,节点短暂的网络不可用,节点重启而暂时停机,或是单个磁盘、节点、机架、网络交换机的分支或主干甚至是数据中心的永久故障。
使用基于 Quorum 的投票协议是复制系统中应对故障的一种方法。如果V个数据副本中每个副本一票,那么一次读操作或是一次写操作必须分别获得Vr票(读 Quorum)或Vw票(写 Quorum )。为了满足一致性,Quorum 必须遵守两个规则。首先,每次读操作必须感知到最近的写入,公式表示为Vr+Vw>V。这个规则确保用于读取的节点集合和用于写入的节点集合相交,并且Vr中至少存在一个具有最新版本的节点。其次,每次写操作必须感知到最近的写入,以避免写冲突,公式表示为vw>2V。
一种容忍单个节点故障的常见方法是将数据复制到(V=3)个节点,设置Vr=2(读 Quorum),Vw=2(写 Quorum)。
我们认为2/3Quorum 这种配比是不够的。要了解原因,首先让我们先了解一下 AWS 中关于可用区(AZ)的概念。AZ 是一个地域的子集,与该区域其他的 AZ 通过低延迟连接,但是 AZ 之间的大多数故障是隔离的,包括电源、网络、软件部署、洪灾等。将数据副本分布在不同的 AZ 中,可确保大规模系统中的典型故障只影响一个数据副本。这意味着只要简单地将这三个副本放在不同的 AZ 中,就可以应对除了较小的单个故障之外的大规模事件。
但是在大型存储系统中,故障的背景噪音意味着在任何给定的时刻,都会有一些磁盘或节点已经故障或正在维修。这些故障或许会独立分布在 AZ A,B 和 C 的每个节点中。然而,可能由于火灾、房屋崩塌、洪水等因素导致 AZ C 故障,此时如果 AZ A 或 AZ B 中的存储副本的节点正好发生了故障,那么就会打破 Quorum 的要求。此时,在2/3Quorum 模型下,我们将丢失两份副本,并无法确定第三份副本是否是最新的。换而言之,虽然每个 AZ 之间单个副本故障是不相关的,但是 AZ 的故障与该 AZ 中的所有节点和磁盘的故障都相关。Quorum 需要容忍 AZ 的故障以及同时发生的背景噪声故障。
在 Aurora 中,我们进行了如下设计:(a) 允许整个 AZ 和一个附加节点(AZ+1)发生故障而不丢失数据,以及(b) 允许整个 AZ 故障不影响写数据。我们将每份数据拷贝 6 份,放在 3 个可用区中,每个可用区保存 2 份拷贝,从而实现了上述目标。我们使用的 Quorum 模型为V=6,Vw=4(写 Quorum),Vr=3(读 Quorum)。通过该模型,我们可以(a) 损失一个 AZ 和一个附加节点(总计 3 个故障节点)而不失去读可用性,并且(b) 失去任意两个节点或者一整个 AZ 还能保持写可用性。确保读 Quorum 使我们能够通过添加其他附加副本拷贝来重建写 Quorum。
2.2 分段存储
让我们思考一下 AZ+1 是否能够提供足够的可持久性。为了在该模型中提供足够的可持久性,必须确保在修复其中一种故障所需的时间内(MTTR - 平均修复时间),发生不相关故障的双重故障概率(MTTF - 平均故障时间)足够低。如果出现双重故障的概率较高,可能会出现一个 AZ 故障,以致打破 Quorum。到目前为止,(我们发现)很难降低 MTTF 在独立故障上的概率。因此我们将重点放在降低 MTTR 上,从而降低双重故障的影响。为此,我们将数据库卷分为固定大小的小段,目前设置的大小是 10G。这些数据段每个都会复制 6 份,组成一个 PG,因此每个 PG 包含 6 个 10GB 的数据段,分布在 3 个 AZ 中,每个 AZ 包含两个数据段。存储卷由一组 PG 组成,在物理上使用 Amazon EC2 挂载 SSD 作为单个节点,由大量存储节点构成。构成存储卷的 PG 集合随着存储卷的增长而增大。我们目前最大支持单个存储卷达 64T。
现在,数据段是背景噪声最小的故障和修复单元。监控是自动修复故障是服务的一部分。在 10Gbps 的网络连接条件下,一个 10G 的数据段可以在 10 秒内被修复。因此,想要打破 Quorum,就必须在 10 秒的时间窗口中同时发生两个数据段故障加上一个 AC 故障,并且 AZ 故障不包含此前故障的两个数据段。即使在我们为客户管理的数据库量级上,通过我们观察到的故障率,这种情况出现的概率非常低。
2.3 可恢复性的运营优势
一旦一个系统设计成对长期故障具有可恢复性,那么应对短期故障也就不在话下。一个可以处理 AZ 长期失联的存储系统,同样也可以处理由于电源事件或需要回滚的异常软件部署造成的短暂中断。一个可以处理几秒钟成员失联的 Quorum,同样也可以处理短暂的网络拥塞或存储节点的高负载。
由于我们的系统对故障的容忍度很高,因此我们可以通过这一点来进行由于数据段不可用的运维操作。举例来说,热点管理可以变得很简单。我们可以将热点磁盘或节点上的某个数据段标记为故障,那么通过迁移系统中的其他非热点节点,Quorum 就可以被快速修复。操作系统和安全补丁在进行修复时,对于存储节点来说是一个短暂的不可用事件。甚至软件更新也可以通过这种方式来进行管理。只需要一次执行一个 AZ,并确保同一个 PG 内没有两个成员同时被处理即可。基于这些,我们可以在存储服务上使用敏捷方法和快速部署。
3. 日志即数据库
在本节中,我们阐述了为什么在第二节中描述的分段复制存储系统上使用传统数据库会带来不可接受的网络 IO 和同步停顿等性能负担。然后介绍了将日志处理转移到存储服务的方法,并通过实验证明我们的方法可以显著的减少网络 IO。最后介绍我们在存储服务中最大程度减少同步停顿和不必要写入的各种技术。
3.1 写放大的负担
我们的模型将存储卷进行分段,并将每个段复制 6 份形成的4/6Quorum,给系统带来了更高的可恢复性。然而,该模型会导致诸如 MySQL 这类的传统数据库性能无法接收,因为这些数据库对于每一个应用写入都会生成许多不同的实际的 I/O。高 I/O 通过复制被放大,从而增加了沉重的 PPS 负担。同时,大量的 I/O 产生了同步点,导致数据管道停顿以及延迟增大。尽管链式复制以及变种可以降低网络开销,但是仍然无法避免同步停顿和延迟放大。
让我们研究一下传统数据库中写入是如何工作的。像 MySQL 这类数据库系统会将数据页写入它创建的对象中(例如堆文件、B 树等),并将 REDO 日志记录到预写日志(WAL)中。每个 REDO 日志包含了被修改数据页的前镜像和后镜像之间的差异。日志记录可以用于数据页在已知前镜像的情况下构建后镜像。
在实际应用时,还必须写入其他数据。举例来说,一对同步镜像的 MySQL 实例,通过主备配置并部署在不同的数据中心从而实现高可用,如图 2 所示。AZ1 中有一个活动的 MySQL 实例,并在 EBS 上配置了网络存储,AZ2 中有一个备用 MySQL 实例,同样在 EBS 上配置了网络存储。使用软件镜像实现主 EBS 卷写入与备用 EBS 卷的同步。
图 2 展示了数据库引擎需要写入的各种类型的数据:重做日志、为了支持任意时间还原归档在 Amazon S3 中的二进制日志、修改后的数据页、为了防止页面损坏而两次写入的数据页以及元数据(FRM)文件。图中还展示了实际 IO 流的顺序如下。在第 1 步和第 2 步中,会写入数据到 EBS 中,然后由 EBS 将其发送到本地 AZ 镜像,并且当两个操作后都完成后才进行确认。接下来,在第 3 步中,使用块级别的同步软件镜像将数据写入备用实例。最后,在第 4 步和第 5 步中,数据被写入到备用 EBS 卷和关联的镜像中。
上述 MySQL 镜像模型无法满足现实需求不仅仅是因为数据是如何写入的,同时也因为哪些数据需要写入。首先,第 1 步、第 3 步和第 5 步是顺序且同步的。由于许多写入是连续的,因此会有累积延迟。由于即使在异步写的情况下,也必须等待最慢的操作完成,使得系统的性能被异常值所决定,因此抖动会被放大。从分布式系统的角度看,这个模型可以视为具有4/4写 Quorum 模型,并且在故障和异常面前十分脆弱。其次,由 OLTP 应用程序引起的用户操作会带来许多不同类型的写入,这些写入却通常表示相同的信息,例如为了防止存储结构中的数据页损坏而设计的两次写入操作。
3.2 迁移 REDO 处理到存储层
传统数据库修改数据页时,会生成一个 REDO 日志记录,并调用日志应用程序将 REDO 日志记录应用到位于内存中的数据页的前镜像,以生成对应的后镜像。事务提交要求必须写入日志,但是数据页的写入可能会延迟。
在 Aurora 中,唯一通过网络进行写入的是 REDO 日志记录。不论是后台写入、Checkpoint 机制还是缓存逐出,在数据库层都不会写入任何数据页。取而代之的是,将日志应用程序推送到存储层,从而在后台或按需生成数据库页。当然,从页面刚开始修改开始的完整链路中生成每个页面的代价是十分昂贵的。因此,我们不停的在后台实现数据库页面,从而避免每次都从头开始重新生成。需要注意后台生成页面从正确性的角度来看是完全可选的;对于引擎来说,日志就是数据库,存储系统中实现的页面仅仅是日志应用程序的缓存。同样还需要注意仅仅只有长链修改的页面才需要重新生成,这与 Checkpont 机制有所不同。Checkpoint 由整个 REDO 日志链的长度控制,而 Aurora 的页面生成仅由给定页的链路长度控制。
尽管扩大了复制写操作,我们的方法仍大大的降低了网络负载,并提供了较好的性能和可持久性。存储服务可以通过并行的方式来扩展 I/O,且不影响数据库引擎的写吞吐量。例如,图 3 展示了一个 Aurora 集群,其中含有一个主实例和跨多个 AZ 部署的多个备用实例。在此模型中,主节点仅将日志记录写入存储服务,并将这个日志记录和元数据更新流式传输到备用实例。IO 流将排好序的日志记录根据目的地进行打包,并将每个包传输到所有的 6 个副本中,副本将其持久化到磁盘,为了满足 Quorum 条件,数据库引擎会等待 6 个中的 4 个进行确认,此时可以认为日志记录被持久化了。副本使用 REDO 日志记录将更改应用到缓存中。
为了测量网络 I/O,我们使用 SysBench 跑了一次写压力测试,测试使用以下两种配置分别写入 100GB 数据集:其一是跨多 AZ 的同步 MySQL 镜像,另一个是 RDS Aurora(其副本分布在多个 AZ 中)。两个实例都在 r3.8xlarge EC2 实例的数据库引擎上运行 30 分钟。
我们的实验结果总结在表 1 中。在 30 分钟的时间内,Aurora 能够处理的事务是 MySQL 镜像的 35 倍。尽管 Aurora 将写放大了 6 倍,并且没有计算 EBS 中的链式复制以及 MySQL 的跨 AZ 写入,但是 Aurora 的数据库节点上的每个事务的 I/O 数量比 MySQL 镜像少 7.7 倍。每个存储节点只能看到六个副本中的一个,因此看到的是未放大的写入,导致需要在该层处理的 I/O 数量减少了 46 倍。通过减少网络中需要写入的数据,使得我们能够更积极的复制数据以提高可持久性和可用性,并且可以通过并发的请求来最大程度减轻抖动的影响。
将处理流程移到存储服务最大程度地减少了崩溃恢复时间,提高了系统的可用性,并且还可以消除由后台处理(例如 Checkpoint、后台数据页写入和备份)引起的抖动。
我们来对比一下崩溃恢复。在传统数据库中,发送崩溃后,系统必须从最近的 Checkpoint 开始并回放日志,确保所有已持久化的 REDO 记录都被应用。而在 Aurora 中,持久化 REDO 记录应用程序连续、异步地发生在存储层,并分布在各个节点中。如果数据页不是最新的,则对于数据页的任何读取请求都可以需要应用一些 REDO 记录。最终对于崩溃处理的过程分散在所有正常的前台处理中,在数据库启动的时候不需要做任何操作。
3.3 存储服务设计点
我们的存储服务的核心设计原则是最小化前台写入请求的延迟。我们将大多数存储处理移至后台。考虑到存储层从峰值到平均请求的差异,我们有足够的时间在前台操作路径之外去执行这些任务。我们也可以用计算来换存储。例如,当存储结点忙于处理前台写请求时,除非磁盘容量已满,否则没有必要运行旧页面版本的垃圾收集(GC)。在 Aurora 中,后台处理与前台处理是负相关的。这不同于传统数据库,传统数据库中页面的后台写入和 Checkpoint 与系统上的前台负载具有正相关性。如果系统上有积压的任务,则将限制前台的活动,防止积压的任务越来越多。由于在我们的系统中,数据段被分散在各个存储节点上,因此一个节点卡死可以轻易被Quorum 写模型处理,该节点表现为一个慢节点(不会影响整个系统的运行)。
我们来进一步研究一下存储节点的各种处理流程。如图 4 所示,它涉及到以下步骤:(1) 接收日志记录并将其添加到内存队列中;(2) 将记录持久化到磁盘上并进行确认;(3) 组织记录并识别日志中的空缺,因为有些包可能会丢失;(4) 与其他节点通信填补空缺;(5) 将日志记录合并到新的数据页中;(6) 定期将日志和新数据页存储到 S3 中;(7) 定期对于旧版本进行垃圾回收;最后(8) 定期验证数据也上的 CRC(循环冗余校验)码。
注意上述每个步骤都是异步的,并且仅仅只有步骤 1 和步骤 2 位于前台处理路径中,可能会影响延时。
4. 日志驱动
在本节中,我们介绍如何从数据库引擎中生成日志,以使持久化状态、运行时状态和副本状态始终保持一致。我们会重点介绍如何在不使用昂贵的 2PC 协议的情况下高效的实现一致性。首先,我们介绍如何在崩溃恢复时避免昂贵的 REDO 操作。接着,我们介绍了一些常规操作,以及我们是如何保证运行时和副本状态保持一致。最后,我们介绍了故障恢复处理流程的细节。
4.1 方案概述:异步处理
由于我们将数据库视为 REDO 日志流(如第 3 节所述),因此我们可以利用日志是一连串有序修改的优势。在实际应用中,每个日志记录都有一条关联的日志序号(LSN),日志序号由数据库生成,单调递增。
上述特性使我们可以简化一致性协议,通过异步的方式来保持状态一致性,而不是使用 2PC 这类易出错且无法容忍故障的协议。从上层来看,我们保持了一致性和可持久性的状态点,并在收到发出去的存储请求的确认后推进状态点。由于每个独立存储节点都有可能遗失一条或多条日志记录,因此它们会与 PG 中的其他节点进行通信,寻找并填补丢失的信息。数据库维护的运行时状态使我们可以单个数据段的进行读取,而不是通过 Quorum 读取,除了在故障恢复时,状态丢失必须重建恢复的时候。
数据库可能存在多个未完成的孤立事务,这些事务完成的顺序可以与实际启动的顺序不同。假设数据库崩溃或重启,这些事务是否进行回滚是分别独立决定的。跟踪部分完成的事务并撤销他们的逻辑由数据库引擎来完成,就如同它写磁盘一样。但是,重启后,在数据库能够访问存储卷之前,存储服务将执行自身的恢复逻辑,恢复时不关注用户级别的事务,而是确保数据库能看到存储的一致性视图,尽管存储本身是分布式的。
存储服务首先确定可以保证所有以前的日志记录均可用的最大 LSN(称为 VCL 或 Volume Complete LSN)。在存储恢复期间,所有 LSN 大于 VCL 的日志记录都要被截断。但是,数据库可以通过标记日志记录并将其标识为 CPL 来进一步进行截断。因此我们可以将 VDL 定义为(所有副本中)最大的 CPL,并且 CPL 必须小于或等于 VCL,所有 LSN 大于 VDL 的日志记录都需要被截断。比如说,即使我们有一份 LSN 1007 之前的完整日志数据,但是数据库声明 CPL 是 900、1000 和 1100,那么我们必须在 1000 处进行截断。虽然有到 1007 的完整记录,但是日志记录只持久化到 1000。
因此完整性和可持久性是不同的,CPL 可以被视为带有某种限制形式的存储系统事务,这些事务必须按序执行。如果客户端不需要这些区分,可以简单地将每个日志记录标记为 CPL。在实际应用时,数据库和存储服务交互流程如下:
每个数据库级别的事务被拆分为多个有序的微事务(MTR),且必须被原子的执行。
每个微事务都由多个连续的日志记录组成。
微事务的最终日志记录是一个 CPL。
在恢复时,数据库与存储服务进行通信,为每个 PG 建立可持久化点,并使用该持久化点来建立 VDL,然后发出命令来截断大于 VDL 的日志记录。
4.2 常规操作
现在,我们开始描述数据库引擎的“常规操作”,并重点依次介绍写、读、提交和副本。
4.2.1 写
在 Aurora 中,数据库持续与存储服务进行交互,维持状态来建立 Quorum,持久化日志记录,注册已提交的事务。例如在常规/前向路径中,当数据库收到确认并为每一批日志记录建立写 Quorum 时,它会推进当前的 VDL。在任何给定时刻,数据库中可以有大量的并发事务处于活动状态,每个事务都会生成自己的 REDO 日志记录。数据库会为每个日志记录分配一个唯一且有序的 LSN,但是遵守以下约束:分配的任何一个 LSN 都不大于当前的 VDL 和 LAL 常数(LSN Allocation Limit,当前设置为 10 Million)的和。这个限制保证了数据库不会比存储系统领先太多,避免当存储或网络无法跟上时,后台压力过大导致写请求被堵塞。
注意每个 PG 的每个段仅看到卷中的一部分日志记录,这些记录会影响段上的页面。每个日志记录都包含一个反向链接,用于标识该 PG 先前的日志记录。这些反向链接可以用于追踪已到达的每个段的日志记录的完整性点,从而建立 SCL,SCL(Segment Complete LSN)用于标识该 PG 已接收到的所有日中记录中最大的 LSN。当存储节点为了查找和交换它们丢失的记录而相互通信时,就会使用 SCL。
4.2.2 提交
在 Aurora 中,事务提交是异步完成的。当客户端提交事务时,处理提交请求的线程将事务放在一边,并记录事务的“提交 LSN”到等待提交事务列表中等待提交,然后继续执行其他工作。这等同于实现了 WAL 协议:当且仅当最新的 VDL 大于或等于事务的提交 LSN 时,事务就提交完成了。随着 VDL 的增长,数据库识别出正在等待提交的事务,使用一个单独的线程将提交确认发送给等待的客户端。工作线程不会等待事务提交完成,它们会继续处理其他正在等待的请求。
4.2.3 读
Aurora 与大多数数据库相同,数据页从缓存中读取,只有当缓存中未找到相关数据页时,才会发起存储 IO 请求。
如果缓存满了,系统会从缓存中逐出一个数据页。在传统数据库系统中,如果该页是脏页,那么在替换前,会将其刷新到磁盘中。这是为了确保后续始终能获取到该数据页最新的数据。而 Aurora 数据库不会在逐出数据页时写磁盘,但它会做出类似的保证:缓存中的数据页必须始终是最新版本。这个保证通过仅当数据页是“页 LSN”(标识与页面的最新更改相关联的日志记录)大于等于 VDL 时,将其从缓存中逐出数据页来实现。该协议可确保:(a) 所有页面的修改都被持久化到日志中,并且(b) 在缓存未命中时,从当前 VDL 开始请求页面的版本以获取最新的持久化版本就足够了。
在正常情况下,数据库不需要使用读 Quorum 建立一致性。当从磁盘中读取数据页时,数据库会建立一个读取点,表示发出请求时的 VDL。然后数据库可以选择一个相对于读取点完整的存储节点,这样就能读取到最新的数据。由存储节点返回的数据页必须与数据库中的微事务(MTR)的语义一致,由于数据库直接管理提交给存储节点的日志记录并跟踪进度(也就是每个数据段的 SCL),它通常知道哪个数据段满足读取要求(SCL 大于读取点的数据段),因此可以直接向有足够数据的段发出读取请求。
假定数据库记录所有未完成的读取,则它可以计算出在任意时间点每个 PG 的最小读取点 LSN。如果存储只读副本,写节点会与它们进行通信获取所有存储节点上每个 PG 的最小读取点 LSN。该值被称为 PG 最小读取点 LSN(PGMRPL),它代表“低水位点”,所有低于该值的 PG 日志记录都是无用的。换而言之,保证存储节点数据段不存在读取点小于 PGMRPL 的读取页请求。每个存储节点都能从数据库中得到 PGMRPL,因此可以合并旧日志记录产生新数据页,然后安全的对这些日志记录进行垃圾回收。
实际的并发控制协议是在数据库引擎中执行的,就好比在传统数据库 MySQL 中,数据库页面和 REDO 段在本地存储一样。
4.2.4 副本
在 Aurora 中,一个写节点和至多 15 个只读节点可以挂载到同一个共享存储卷上。因此,只读节点在消耗存储或磁盘写入操作时,不会增加额外成本。为了最小化延迟,由写节点生成日志流并发送到存储节点,并同时发给所有的只读副本。在读节点中,数据库按序消费日志流中的每个日志记录。如果日志记录引用缓存中的数据页,则将使用日志应用程序将 REDO 操作应用到缓存的页面上。否则就直接丢弃。请注意,从写节点的角度来看,读节点消费日志记录是异步进行的,写节点确认用户事务提交与只读副本是分开的。只读副本在应用日志记录时遵守以下两条重要规则:(a) 只有 LSN 小于或等于 VDL 的日志记录才会被应用,并且(b) 属于微事务一部分的日志记录会被原子的应用到只读副本的缓存中,从而确保副本可以看到所有数据库对象的一致性视图。在实际应用中,每个只读副本通常会落后写节点一小段时间(20ms 或更短)。
4.3 恢复
大多数传统数据库使用诸如 ARIES 这类的恢复协议,这类协议依赖可以表示所有已提交事务准确内容的预写日志 WAL。这些系统也会将脏页刷新到磁盘,并将 Checkpoint 记录到日志中,从而定期地对数据库建立 Checkpoint,建立粗粒度的持久性点。系统重启后,页面可能会丢失一些已提交的数据或包含未提交的数据。因此,在崩溃恢复时,这类系统需要处理从上一次 Checkpoint 开始的所有 REDO 日志记录,通过日志应用程序来应用与数据库页相关的每一条日志记录。这个处理流程使数据库在故障点达成了一致性状态,在这之后可以执行相关的 REDO 日志记录来回退崩溃时未完成的事务。(在这种处理流程下)崩溃恢复是一个非常昂贵的操作。减少 Checkpoint 间隔可能会有所帮助,但是会影响前台事务。但是 Aurora 不需要做这样的折中。
传统数据库极大的简化规则是,前台处理路径和故障恢复同步操作时使用同样的 REDO 日志应用程序。我们在 Aurora 中也遵循同样的规则,除了将 REDO 日志应用程序与数据库解耦并在存储节点中并行且始终在后台运行之外。数据库启动后,它将于存储服务协作执行存储卷恢复。因此,即使每秒处理超过 100,000 条写语句时崩溃,Aurora 数据库也能非常快速地恢复(通常在 10 秒以内)。
数据库在崩溃后的确需要重新建立其运行时的状态。在这种情况下,数据库会与每个 PG 进行通信,以此重建数据段的读 Quorum,保证达成写 Quorum 的任何数据都能被发现。一旦数据库为每个 PG 建立了读 Quorum,就可以重新计算 VDL,并通过生成截断范围(新的 VDL 到当前数据库目前已分配的最大的 LSN)来对数据进行截断。数据库可以推断出此上限的原因是它负责分配 LSN,并限制了高于 VDL(如前面所述,值为 1000 万)的分配范围。截断范围使用纪元编号进行版本控制,并持久化到存储服务中,从而保证在中断恢复和重启的情况下,截断不会产生歧义。
数据库在崩溃时,仍需要执行 UNDO 恢复以撤销进行中事务的操作。然而,UNDO 恢复可以在系统恢复在线,从 UNDO 段文件中建立这些进行中事务的列表之后再进行。
标签:存储,Aurora,写入,节点,故障,课程,日志,MIT6.824,数据库 From: https://www.cnblogs.com/cnyuyang/p/18431956