原文地址:AlloyDB for PostgreSQL under the hood: Intelligent, database-aware storage
原文作者:Ravi Murthy
今天,在 Google I/O 上,我们发布了 AlloyDB for PostgreSQL,这是一个完全托管的、与 PostgreSQL 兼容的数据库,用于要求苛刻的企业级事务和分析工作负载。想象一下 PostgreSQL 加上最好的云:弹性存储和计算、智能缓存和 AI/ML 支持的管理。此外,AlloyDB 提供了无与伦比的性价比:在我们的性能测试中,它在事务工作负载上的速度比标准 PostgreSQL 快 4 倍以上,分析查询速度高达 100 倍,所有这些都具有简单、可预测的定价。AlloyDB 专为任务关键型应用程序而设计,提供广泛的数据保护和行业领先的 99.99% 可用性。
多项创新为 AlloyDB for PostgreSQL 的性能和可用性提升奠定了基础。在我们的“AlloyDB for PostgreSQL under the hood”系列的第一部分中,我们将介绍针对 PostgreSQL 优化的智能、数据库感知、水平可扩展的存储层。
计算和存储的分解
AlloyDB for PostgreSQL 建立在计算和存储分解的基本原则之上,旨在利用堆栈的每一层的分解。
AlloyDB 首先将数据库层与存储分离,引入针对 PostgreSQL 优化的新智能存储服务。这减少了 I/O 瓶颈,并允许 AlloyDB 通过使用日志处理系统将许多数据库操作卸载到存储层。存储服务本身也分解计算和存储,允许块存储与日志处理分开扩展。

多年来,数据库中计算和存储的分解不断发展。在早期的方法中,尽管存储的大小可以独立于计算层,但整个系统仍然相当静态,缺乏弹性。通过构建云级存储解决方案,下一代数据库系统提高了存储弹性,但仍然存在存储集群过大或在工作负载高峰(热点)的情况下缺乏 IO 容量的问题。
借助 AlloyDB,即使在存储层,完全分解的架构也允许它作为一个弹性的分布式集群工作,可以动态适应不断变化的工作负载,增加容错能力,提高可用性,并启用具有成本效益的读取池,水平扩展读取吞吐量。整个堆栈中的多层缓存根据工作负载模式自动分层,为开发人员提供更高的性能,同时保持云原生存储的规模、经济性和可用性。结合起来,AlloyDB 架构的这些方面标志着数据库分解发展的下一步,并有助于 AlloyDB 的卓越性能和可用性。
单体设计的问题
传统上,PostgreSQL 数据库采用单体设计,将存储和计算资源共存于一台机器中。如果您需要更多存储容量或计算性能,您可以通过迁移到更强大的服务器或添加更多磁盘来扩展系统。当进一步扩展不再可能或成本效益更高(更强大的服务器变得越来越昂贵)时,您可以使用复制来创建数据库的多个只读副本。
这种方法有其局限性:故障转移时间较长且难以预测,因为它们取决于数据库负载和配置。此外,只读副本有自己的、滞后且昂贵的数据库副本,这使得扩展读取容量和管理副本滞后变得更加困难。因此,具有紧密耦合存储和计算的单一数据库的弹性是有限的。通过分解计算和存储,AlloyDB 能够克服许多这些限制。
为了进一步提高数据库层的可扩展性,超出单台(虚拟)机的容量,AlloyDB 允许您添加多个只读副本实例,以支持主数据库实例进行只读查询处理,而无需额外的数据库副本:存储层跨区域分布并可从任何服务器访问,因此您可以快速构建成本低廉(因为每个副本实例不需要自己的存储)和最新的只读副本实例。从根本上说,这些设计原则允许我们创建一个平台,将功能从主数据库实例单体中移出,并将其转换为提供更好性能、可扩展性、可用性和可管理性的云原生实现。
AlloyDB 设计概述
AlloyDB 存储层是一个分布式系统,由三个主要部分组成:
一种低延迟的区域日志存储服务,用于非常快速的预写日志 (WAL) 写入
处理这些 WAL 记录并生成“物化”数据库块 的日志处理服务(LPS)
容错、分片、区域块存储,即使在区域存储发生故障的情况下也能确保持久性。
下面的图 2 显示了日志处理服务及其与 PostgreSQL 数据库层和持久存储的集成的高级概念概述。主数据库实例(只有一个)持久化 WAL 日志条目,将数据库修改操作(如 INSERT/DELETE/UPDATE)反映到低延迟区域日志存储。从那里,日志处理服务 (LPS) 使用这些 WAL 记录进行进一步处理。由于日志处理服务完全了解 PostgreSQL WAL 记录的语义和 PostgreSQL 存储格式,它可以不断重放这些 WAL 记录所描述的修改操作,并将最新的数据库块物化到分片的区域存储系统. 从那里,
为了使副本实例的本地缓存保持最新,AlloyDB 还将 WAL 记录从主实例流式传输到副本实例,以通知它们最近的更改。如果没有有关更改块的信息,副本实例中的缓存块可能会变得任意陈旧。

这种方法的主要好处是什么?让我们更详细地考虑这种设计的一些含义:
甚至在存储层内也能实现完全的计算/存储分解。LPS 可以根据工作负载模式进行横向扩展,并在需要避免热点时透明地添加更多计算资源来处理日志。由于日志处理器纯粹通过计算附加到共享区域存储,因此它们可以灵活地横向扩展/缩减,而无需复制任何数据。
-
存储层复制:通过跨多个区域同步复制所有块,存储层自动保护系统免受区域故障的影响,而不会对数据库层产生任何影响或修改。
-
高效的 IO 路径/无整页写入:对于更新操作,计算层仅将 WAL 记录传递给存储层,存储层不断重放它们。在这种设计中,不需要检查点数据库层,也不需要任何理由将完整的数据库块发送到存储层(例如,防止页面撕裂问题)。这使得数据库层可以专注于查询处理任务,并且可以有效地使用数据库和存储层之间的网络。
-
低延迟 WAL 写入:使用低延迟的区域日志存储允许系统在事务提交操作的情况下快速刷新 WAL 日志记录。因此,事务提交是一个非常快速的操作,即使在峰值负载时系统也能实现高事务吞吐量。
-
快速创建只读副本实例:由于存储服务可以为任何区域中的任何块提供服务,因此来自数据库层的任意数量的只读副本实例可以附加到存储服务并处理查询,而无需数据库的“私有”副本。只读副本实例的创建速度非常快,因为可以按需从存储层增量加载数据——在开始查询处理之前无需将数据库的完整副本流式传输到副本实例。
-
快速重启恢复:由于在线操作期间日志处理服务不断重放WAL日志记录,重启恢复时需要处理的预写日志量最小。结果,系统重新启动显着加快(因为与 WAL 相关的恢复工作保持在最低限度)。
-
存储层备份:备份操作完全由存储服务处理,不影响数据库层的性能和资源。
写操作的生命周期
让我们通过跟踪数据库修改操作的路径来进一步探索系统的设计(图 3)。它从客户端开始,例如,通过客户端的 TCP 连接到数据库层上的主实例发出 SQL INSERT 语句。主实例处理语句(在内存中更新其数据和索引结构)并准备捕获更新操作语义的 WAL 日志记录。事务提交时,首先将这条日志记录同步保存到低延迟区域日志存储中,然后在下一步被日志处理服务异步拾取。
请注意,存储层被有意分解为单独的组件,以优化存储层执行的单独任务——日志存储、日志处理和块存储。为了减少事务提交延迟,尽可能快地持久存储日志记录并实现事务持久性非常重要。由于 WAL 日志写入是一种仅追加操作,因此 AlloyDB 专门针对此用例优化了高性能、低延迟的存储解决方案。在第二阶段,需要通过将 WAL 日志记录应用到它们所引用的块的先前版本来处理它们。为此,存储层的 LPS 子系统执行随机块查找,并以高性能和可扩展的方式应用 PostgreSQL 的重做处理逻辑。
为了确保物化块的区域持久性,日志处理服务 (LPS) 的多个实例在该区域的每个区域中运行。必须处理每条日志记录,并且生成的缓冲区需要持久地存储在分片的区域块存储中(见下文),以最终从区域日志存储中删除日志记录。

读操作的生命周期
同样,读取以发送到数据库服务器的 SQL 查询开始;这可以是主实例,也可以是用于只读查询处理的(可能很多)副本实例之一(这两个路径都在图 4 中可视化)。数据库服务器执行与传统 PostgreSQL 系统相同的查询解析、规划和处理。如果所有需要的块都存在于其内存驻留缓冲区缓存中,则数据库根本不需要与存储层交互。为了允许非常快速的查询处理,即使在工作集不适合缓冲区缓存的情况下,AlloyDB 也将超快速块缓存直接集成到数据库层中。此高速缓存显着扩展了缓冲区高速缓存的容量,从而在这些情况下进一步加速系统。
但是,如果两个缓存中都缺少一个块,则将相应的块获取请求发送到存储层。除了要检索的块号之外,该请求还指定了读取数据的日志序列号 (LSN)。此处使用特定 LSN 可确保数据库服务器在查询处理期间始终看到一致的状态。当从 PostgreSQL 缓冲区缓存中逐出块并随后重新加载它们时,或者在遍历复杂的多块索引结构(如可能(结构上)同时修改的 B-树)时,这一点尤其重要。

在存储层,日志处理服务也负责服务块获取请求。每个 LPS 都有自己的 PostgreSQL 缓冲区缓存实例——如果请求的块已经在 LPS 缓冲区缓存中,它可以立即返回到数据库层而无需任何 I/O 操作。如果请求的块不存在于缓存中,LPS 从分片的区域存储中检索块并将其发送回数据库层。日志处理服务还必须做一些记账,以跟踪哪些块具有尚未处理的日志记录。当对这样一个块的请求到达时(我们预计这种事件很少见,因为数据库层只请求已从缓存中逐出然后被引用的块),读取请求必须停止,直到该日志记录的重做处理完成. 因此,为了避免这种停顿,LPS 层上的 WAL 处理高效且可扩展非常重要,因此它甚至可以处理最苛刻的企业工作负载。我们将在下一节中更详细地讨论这一点。
存储层弹性
到目前为止,我们已经将日志处理服务作为单个进程(在每个区域中)进行了讨论。但是,对于要求苛刻的企业工作负载,只有一个 LPS 进程可能会产生可伸缩性问题,因为 LPS 既需要持续应用 WAL 记录,又需要服务来自主实例和多个副本实例的读取请求。
为了解决这个问题,数据库持久性被水平划分为称为分片的块组。分片和 LPS 资源都可以水平且独立地扩展。
每个分片在任何时候都被分配给一个 LPS,但每个 LPS 可以处理多个分片。分片到 LPS 的映射是动态的,允许存储层通过扩展 LPS 资源的数量和重新分配分片来弹性响应增加的访问模式。这不仅允许存储层扩展吞吐量,还可以避免热点。
让我们在这里考虑两个示例:在第一种情况下,整体系统负载增加,几乎所有分片都收到比以前更多的请求。在这种情况下,存储层可以增加 LPS 实例的数量,例如将它们加倍。然后,新创建的日志处理服务器实例通过接管它们的一些分片来卸载现有实例。由于这种分片重新分配不涉及任何数据复制或其他昂贵的操作,因此它非常快速且对数据库层不可见。
另一个分片重新分配非常有用的例子是一小部分分片突然在系统中变得非常流行的情况(例如,在超级碗广告之后经常请求存储在数据库中的某个产品系列的信息)。同样,存储层可以动态做出反应——在最极端的情况下,通过将观察到工作负载峰值的每个分片分配给专门处理分片负载的专用 LPS 实例。因此,通过重新分片和 LPS 弹性,即使在工作负载高峰的情况下,系统也可以提供高性能和吞吐量,并且如果工作负载再次减少,也可以减少其资源占用。对于数据库层和最终用户,这种动态调整大小和存储层弹性是完全自动的,不需要用户操作。

存储层复制和恢复
AlloyDB 的目标是即使在区域性故障的情况下(例如,在数据中心断电或发生火灾的情况下)也能提供数据持久性和高系统可用性。为此,每个 AlloyDB 实例的存储层分布在三个区域中。每个区域都有一个完整的数据库状态副本,它通过应用来自上述低延迟区域日志存储系统的 WAL 记录来不断更新。

图 6 显示了跨三个区域的完整系统,具有多个日志处理服务器 (LPS),并且每个服务器可能有多个分片。请注意,每个分片的副本在每个区域中都可用。
使用这种架构,可以以最小的开销执行块查找操作。每个区域都有自己的完整数据库状态副本,因此数据库层的块查找操作不需要跨越区域边界。此外,存储层在所有区域中持续应用 WAL 记录,数据库层为其请求的每个块提供目标版本 LSN(见上文),因此在查找操作期间无需建立读取仲裁。
如果整个区域变得不可用,存储层可以通过集成来自同一区域的新区域来替换故障区域,并使用完整数据库状态的副本填充它。如图 6 所示,这是通过确保每个分片的副本在新区域中可用,并通过运行日志处理服务以使用最新的 WAL 记录不断更新分片来完成的。因此,存储层在内部处理所有区域故障转移,而无需数据库层的任何编排或辅助活动。
除了存储层的这些内置功能外,AlloyDB 还集成了手动和自动计划的备份操作,以防止应用程序级或操作员故障(如意外删除表)。
AlloyDB的智能存储能为您做什么
总而言之,AlloyDB for PostgreSQL 分解了数据库的计算层和存储层,并通过使用日志处理系统将许多数据库操作卸载到存储层。即使在存储层,完全分解的架构也允许它作为一个弹性的分布式集群工作,可以动态适应不断变化的工作负载,增加容错能力,提高可用性,并启用具有成本效益的读取池,以线性扩展读取吞吐量。卸载还允许主实例更高的写入吞吐量,因为它可以完全专注于查询处理并将维护任务委托给存储层。结合起来,AlloyDB 的智能、数据库感知存储层的这些方面有助于 AlloyDB 的卓越性能和可用性。
要亲自试用 AlloyDB,请访问cloud.google.com/alloydb。一定要继续关注我们关于 AlloyDB Columnar Engine 的下一篇文章。
如果没有我们工程团队的杰出贡献,本文和后续文章中描述的 AlloyDB 技术创新是不可能实现的。




