文章

论文学习 PolarDB-MP: A Multi-Primary Cloud-Native Database via Disaggregated Shared Memory

论文学习 PolarDB-MP: A Multi-Primary Cloud-Native Database via Disaggregated Shared Memory

论文学习 PolarDB-MP: A Multi-Primary Cloud-Native Database via Disaggregated Shared Memory

现有系统存在的问题

现有基于主从架构的分布式数据库,

  • 受限于单主节点写入性能,无法满足高并发写入场景的需求。
  • 主节点崩溃后、从节点晋升为主节点时,存在短暂的不可用时间。

理想的方案

  • 多主架构,每个节点都可以处理写入请求。
  • 无缝故障转移

现有多主架构的方案:

  • shared-nothing
    • 问题:数据分区,跨分区的事务需要两阶段提交,开销大。
  • shared-storage
    • 问题:
      • 昂贵的分布式锁(用于解决写冲突)和网络开销。
      • 它们通常部署在专用机器上,对于动态云环境来说过于死板,成本高

本文方法概括

为了应对这些挑战,本文提出了 PolarDB-MP,这是一种通过分解共享内存(以及共享存储)实现的多主云原生数据库。PolarDB-MP 继承了 PolarDB 的分解共享存储模型,允许所有主节点平等地访问存储。这使得事务可以在一个节点中处理,而无需诉诸分布式事务。与乐观并发控制相比,PolarDB-MP 采用悲观并发控制来缓解由写入冲突导致的事务中止。与其他依赖日志重放和页面服务器实现节点间数据一致性的云数据库不同,PolarDB-MP 使用分解共享内存来实现高效的缓存和数据一致性。随着云供应商数据中心中 RDMA 网络的可用性不断提高 [54],PolarDB-MP 与 RDMA 进行了复杂的协同设计,以提高其性能。

PolarDB-MP 的核心组件是 Polar 多主融合服务器 (PMFS),它基于分解式共享内存构建。PMFS 包括事务融合、缓冲区融合和锁融合。 事务融合有助于跨多个节点实现事务可见性和排序。它利用时间戳 Oracle (TSO) 进行排序,并在每个节点上分配共享内存来存储其他节点可远程访问的本地事务数据。 这种通过共享内存进行分散式事务管理的方法可确保全局事务处理的低延迟和高性能。 缓冲区融合实现了分布式缓冲池 (DBP),同样基于分解式共享内存,所有节点均可访问。节点既可以远程将修改后的数据推送到 DBP,也可以从 DBP 检索数据。此设置允许将更改从一个节点快速传播到其他节点,从而确保缓存一致性和快速数据访问。 锁融合可有效管理页面级和行级锁定方案,从而支持跨不同节点的并发数据页面访问,同时确保物理数据一致性并保持事务一致性。

PolarDB-MP 还巧妙地管理跨多个节点的预写日志,使用逻辑日志序列号 (LLSN) 为来自不同节点的日志建立部分顺序。LLSN 确保与页面相关的所有日志都按照生成时的顺序维护,从而保持数据一致性并简化恢复过程。另一方面,PolarDB-MP 基于 LLSN 框架设计了一种新的恢复策略,有效地管理崩溃恢复场景。

此外,PolarDB-MP 的消息传递和 RPC 操作通过高度优化的 RDMA 库得到增强,从而提高了整体效率。

整体架构

整体架构

PMFS 采用分解式共享内存实现,通常由多个节点组成,提供高可用性。

PMFS 包含三个核心组件:事务融合、缓冲区融合和锁融合。

事务融合旨在管理全局事务处理,保证事务的 ACID。它维护一个全局时间戳 Oracle (TSO) 来命令事务提交。此外,事务融合支持全局事务可见性,以实现多版本并发控制 (MVCC) 下的快照隔离。

配备分布式缓冲池 (DBP) 的缓冲区融合在维护所有节点的缓冲区一致性方面发挥着至关重要的作用。 DBP 缓冲多个数据页以实现快速访问。当一个节点对数据页进行更新时,它会在适当的时机将更新后的页面推送到 DBP。随后,如果另一个节点需要此页面,它可以直接从 DBP 检索最新版本。主节点和 DBP 之间的连接由高速 RDMA 网络提供,确保数据页在不同节点之间快速移动。DBP 利用分解的共享内存来存储这些数据页。由于每个节点都有一个本地缓冲区,因此缓冲区融合还负责为所有节点实现缓冲区一致性。

锁融合负责实现两种锁定协议:页面锁定 (PLock) 和行锁定 (RLock)。PLock 协议确保不同节点并发访问时页面的物理一致性。节点只有持有相应的独占/共享 PLock 时才能写入或读取页面。同时,RLock 协议保证用户数据的事务一致性,并且通常遵循两阶段锁定协议。为了减少 RLock 请求引起的消息传递开销并避免锁定状态的集中元数据,PolarDB-MP 将行锁定信息嵌入行数据本身,仅在锁融合中维护等待关系。

此外,PolarDB-MP 提出了逻辑日志顺序号(LLSN)来对跨不同节点的重做日志(预写日志)进行排序。LLSN 为不同节点生成的所有重做日志提供了分区顺序,从而保证了每个页面的重做日志都按照其生成的顺序进行维护。此方案确保了系统崩溃后恢复的正确性。

在 PolarDB-MP 中,主节点和 PMFS 之间的所有通信都利用单边 RDMA 或基于 RDMA 的 RPC。这种方法确保了高效且低延迟的消息传递。与 PolarDB 类似,PolarDB-MP 还包含一个备用节点,以确保跨区域的高可用性。主集群中发生的更改使用预写日志同步到备用集群,从而即使在发生区域故障时也能保持数据库的完整性和连续性。

具体设计和实现

事务融合

在 PolarDB-MP 等多主数据库中,事务排序和可见性对于保持隔离性和一致性至关重要。为了实现高性能并保持简单性,PolarDB-MP 采用时间戳 Oracle (TSO) 进行事务排序。TSO 是 PMFS 上事务融合的一个组成部分。当事务到达提交阶段时,它会向 TSO 请求提交时间戳 (CTS)。此 CTS 是一个逻辑的、增量分配的时间戳,可确保保持有序的事务处理。CTS 通常使用单边 RDMA 操作来获取,该操作通常在几微秒内完成,并且在我们的测试中发现它不是瓶颈。

为了高效管理集群中的所有事务信息, PolarDB-MP 采用一种去中心化的方法将这些信息分布在所有节点上。PolarDB-MP 中的每个节点都保留一小部分内存来存储其本地事务信息。一个节点可以通过单边 RDMA 远程访问另一个节点的事务信息。图 3 说明了这种设计。每个节点都在事务信息表 (TIT) 中维护其本地事务的信息。TIT 在管理事务中起着至关重要的作用,它为每个事务维护四个关键字段:

  • pointer:事务对象指针
  • CTS:标记事务的提交时间戳
  • version:标识同一时隙中的不同事务
  • ref:指示是否有任何事务正在等待该事务释放其锁

当事务在节点上启动时,将为该事务分配一个本地递增且唯一的 ID。然后为该事务分配一个空闲的 TIT 槽(由空事务指针标识),存储其指针并适当设置其他字段。由于 TIT 槽可以重复使用,版本字段区分在不同时间占用相同槽的事务,并随着每个新事务而递增。

最初,事务的 ref 设置为零,CTS 设置为 CSN_INIT。

为了全局标识事务,PolarDB-MP 将 node_id、trx_id、slot_id 和 version 组合成一个全局事务 ID(g_trx_id)。使用此 g_trx_id,任何节点都可以通过 RDMA 从目标节点远程访问事务的 CTS。

整个事务的信息分布在不同的节点上。每个节点只需维护其本地事务的元数据。事务 ID 和 TIT 槽可以在本地分配,而无需与协调器通信,从而减少开销并简化多主系统中的事务管理。 此外,PolarDB-MP 为每行添加了两个额外的元数据字段来存储 g_trx_id 和 CTS。这两个字段可以帮助确定数据的可见性。

alt text

事务可见性

与大多数现代数据库一样,PolarDBMP 也实现了 MVCC,这是一种流行的技术,用于在允许并发事务的同时保持一致性。在 PolarDB-MP 中,更新行的过程涉及将当前全局事务 ID (g_trx_id) 存储在行的元数据中。此 g_trx_id 对于跟踪上次修改行的事务至关重要。当事务到达提交阶段时,它会更新受该事务影响的行的元数据中的 CTS,前提是这些行仍在缓冲区中。如果在提交事务时行不再位于缓冲区中,则其 CTS 保持为默认值 (CSN_INIT)。

这两个元数据,g_trx_id 和 CTS,对于确定行对给定事务的可见性至关重要。PolarDB-MP 中的每个事务都分配有一个读取视图,该视图由其自己的 g_trx_id 和从 TSO 获取的当前 CTS 组成。此读取视图在 MVCC 机制中起着至关重要的作用。 如果行在事务的读取视图之前提交,则该行对事务可见。当事务执行读取操作时,它最初会访问行的最新版本。但是,如果此版本对事务不可见(基于其读取视图),它会利用撤消日志来构建行的先前版本。此过程持续进行,直到找到事务可见的行版本。

alt text

然而,在多主数据库中,挑战在于如何获取行的 CTS,因为该行可能被另一个节点更新。PolarDBMP 使用算法 1 中概述的 TIT 设计来解决这个问题。如果行的 CTS 字段填充了有效值,而不是初始化值 (𝐶𝑆𝑁_𝐼𝑁𝐼𝑇 ),它可以直接从行中获取 CTS (行 2-5)。但是,在某些情况下,行的 CTS 未填充(通常是因为行在事务提交时从缓冲区中被逐出),TIT 用于获取此行的 CTS。它首先需要从行的元数据字段中检索事务 ID (g_trx_id)。然后它可以用这个 g_trx_id 获取相应的 TIT 槽。如果 g_trx_id 属于当前节点,则可以直接从本地 TIT 读取 TIT 槽(第 9 行)。否则,需要从目标节点进行单边远程 RDMA 读取(第 11 行)。确保 TIT 中的 g_trx_id 版本与正在检查的版本匹配至关重要(第 13 行)。不匹配表示 TIT 槽已被其他事务重用,这意味着原始事务已经提交。在这种情况下,我们可以返回最小 CTS 值以指示此行对所有事务都可见(第 15 行)。这是因为只有当 TIT 槽的 CTS 小于所有活动视图的 CTS 时,才会释放和重用 TIT 槽。如果槽有效,我们可以从槽中获取 CTS。如果事务仍处于活动状态,它将返回最大 CTS 值以指示它对除其自身之外的任何事务都不可见(第 16 行)。

TIT 的设计特别适合 RDMA,允许通过单边 RDMA 接口跨节点高效地远程访问 TIT 槽。在系统启动期间,每个节点都会将其 TIT 的起始地址与其他节点同步。系统中的每个全局事务 ID 都包含对特定 TIT 槽的引用,指示其在 TIT 中的位置或偏移量。当节点需要从位于另一个节点的 TIT 槽访问信息时,它首先计算远程地址。此计算基于 TIT 的同步起始地址和特定 TIT 槽的偏移量。一旦确定了远程地址,节点就可以使用单边 RDMA 直接从远程节点的 TIT 槽访问所需的数据。

TIT 回收

TIT 的内存占用有限。为了有效地管理这一点,后台线程会回收并释放已使用的 TIT 槽以供重用。当事务的更改对所有其他事务都可见时,事务的 TIT 槽就有资格回收。这意味着如果一个槽被重用,相关事务的更改对任何事务都是可见的。为了实现这一点,每个节点运行一个后台线程,将其最小视图发送到事务融合。事务融合将这些视图整合起来形成一个全局最小视图,然后将其广播到所有节点。如果节点的 CTS 小于全局最小视图的 CTS,则节点回收 TIT 槽。

时间戳提取

对于读取事务,需要从 TSO 获取当前 CTS。为了减少频繁提取 CTS 的开销,尤其是在读取已提交隔离下,我们利用了 PolarDBSCC [54] 中的线性 Lamport 时间戳方法。如果请求是在请求到达后获得的,则它允许请求重用最近提取的时间戳,从而显著减少时间戳提取操作的数量,同时保持一致的隔离级别。这种方法的正确性和有效性已经在 PolarDB-SCC [54] 中得到证明。

缓冲区融合

每个 PolarDB-MP 节点都可以更新任何数据页,从而导致不同节点之间频繁传输页面。为了促进快速的跨节点数据移动,PolarDB-MP 提出了缓冲区融合,其中节点将其数据页推送到缓冲区融合的分布式缓冲池 (DBP),随后另一个节点可以从此 DBP 访问其对等节点修改的页面。在这种情况下,页面可以在不同节点之间高效移动,延迟低。此外,DBP 使用分解的共享内存作为其缓冲区,从而实现可观且可扩展的缓冲池。

alt text

图 4 展示了 Buffer Fusion 设计。每个节点都有自己的本地缓冲池 (LBP),它是 Buffer Fusion DBP 的一个子集。在 LBP 中,我们为每个页面的元数据引入了两个额外的字段:valid 和 r_addr。valid 字段表示该页面是否已被其他节点修改,r_addr 指向该页面在 DBP 中的地址。当从其 LBP 访问页面时,节点首先检查页面的有效性。如果无效,则通过单边 RDMA 接口使用其 r_addr 从 DBP 检索页面。在 Buffer Fusion 中,它还为每个页面维护一些元数据,以便跟踪具有该页面副本的节点 ID、其无效标志的地址以及该页面在 DBP 中的地址。当页面的新版本存储在 DBP 中时,Buffer Fusion 通过无效标志的地址远程使其他节点上的副本无效。在 LBP 中,脏页(自加载到 LBP 后被修改过的页面)会定期在后台刷新到 DBP,或者在释放相应的 PLock 时按需刷新。PolarDB-MP 采用“不强制提交”策略,该策略通常用于提高许多数据库的性能。在 PolarDB-MP 中,在将脏页刷新到 DBP 之前,PolarDBMP 还会强制将相应的日志存储起来。这确保了如果页面已经刷新到 DBP,则可以将其从 LBP 中逐出,并且如果 DBP 发生故障,则可以从日志中恢复该页面。

页面访问

在 PolarDB-MP 中,如果页面在 LBP 中且有效,则每个节点都可以直接访问该页面。如果无效,则通过单边 RDMA 接口从 DBP 检索页面。如果页面不在 LBP 中,则对 Buffer Fusion 的 RPC 调用将检查其是否在 DBP 中。如果页面在 DBP 中,它会将主节点的 ID 添加到页面的活动节点,并将页面的地址返回给节点,以便从 DBP 进行远程读取。如果页面不在 LBP 或 DBP 中,则从共享存储中读取。一旦由节点加载,页面就会注册到 DBP 并远程写入其中。跨不同节点的并发页面访问以及数据库内部结构(如 B 树页面)的一致性使用 PLock 来维护,这将在后续部分中详细介绍。

锁融合

Lock Fusion 实现了页面锁定 (PLock) 和行锁定 (RLock) 协议。PLock 类似于单节点数据库中的页面闩锁,可确保对页面的原子访问和内部结构的一致性。另一方面,RLock 遵循两阶段锁定协议(通常在许多数据库中使用),可跨节点维护事务一致性。

PLock 协议

PLock 旨在保持物理数据一致性。它不适用于单个节点内的并发页面访问。单个节点内的内部页面并发控制仍然与以前相同。PLock 在节点级别进行管理,如图 5 所示。每个节点都会跟踪其持有或正在等待的 PLock,引用计数表示使用特定 PLock 的线程数。Lock Fusion 维护所有 PLock 信息,跟踪每个锁的状态。在 PolarDB-MP 中,在对页面执行任何更新/读取之前,节点必须持有相应的 X/S PLock。当节点需要 PLock 时,它首先检查其本地 PLock 管理器以查看它是否已经拥有所需的或更高的锁级别。如果没有,它通过基于 RDMA 的 RPC 向 Lock Fusion 请求 PLock。Lock Fusion 处理请求,在响应之前检查是否存在冲突。如果存在冲突,则请求节点将被暂停,并在其 PLock 请求被批准时被唤醒。此外,当节点释放 PLock 时,此更改将传达给 Lock Fusion,然后 Lock Fusion 会更新锁的状态。Lock Fusion 还会通知任何等待释放 PLock 的节点,使它们能够继续执行其操作。

alt text

延迟释放

由于时间局部性 [5],页面在被访问后不久将被再次引用。因此,PolarDB-MP 实施了一种策略来最小化与 PLock 相关的 RPC 开销。节点不会在使用后立即将其 PLock 释放回 Lock Fusion,而是减少 PLock 的引用计数。一旦此计数降至零,锁即可释放,但仍由节点暂时保留。如果同一节点需要再次获取 PLock,并且请求的锁类型不比当前持有的类型强,则可以在本地授予 PLock。此方法有效地减少了在同一节点上频繁访问的页面的 RPC 开销。 在不同节点请求冲突的锁类型的情况下,Lock Fusion 会通过向当前持有锁的节点发送协商消息进行干预。此消息提示持有节点在其引用计数达到零后释放锁。

如果节点已经拥有相同或更强类型的 PLock,则可以在本地授予 PLock。但是,为了防止其他节点可能出现锁匮乏的情况,当节点收到 PLock 的协商消息时,它不能自主保证其内部事务使用此 PLock。相反,它必须与 Lock Fusion 进行通信,后者以先进先出的顺序管理向节点授予锁。这种方法可确保在不同节点之间公平高效地分配锁,从而保持本地优化和全局资源分配之间的平衡。

物理一致性

PLock 维护物理数据和内部结构的物理一致性。要更新或读取任何节点上的页面,该节点必须首先获取该页面的适当的独占或共享 PLock。此过程可确保当节点在本地更新页面时,其他节点无法读取此页面,因为它们无法获取共享 PLock。如果某个节点即将释放独占 PLock,并且相应的页面已被修改,则该节点会将此更新的页面推送到 DBP。同时,它将通知 Buffer Fusion 使其他节点上的页面无效。因此,如果另一个节点在持有必要的 PLock 后请求此页面,它将发现其本地版本现在无效,随后将从 DBP 检索页面的最新版本。另一方面,为了保证内部结构(如 B 树)的一致性,PolarDB-MP 采用了与单节点数据库类似的方法。对 B 树结构的更改(例如页面拆分或合并)在内部微型事务中执行。在这些操作期间,相应页面的 PLock 被保留,直到微型事务提交。它确保无论是在同一节点内还是在其他节点上,任何事务都不会遇到不一致的 B 树结构。因此,这种对 PLock 的精心管理在维护数据库在其多主环境中的结构和数据的完整性和一致性方面起着至关重要的作用。

RLock 协议

在单节点数据库中,通常使用行级锁定来确保事务一致性和隔离性。在 PolarDB-MP 中,多个事务可能在不同的节点上同时运行,并可能更新相同的数据,因此全局行锁定 (RLock) 协议必不可少。为了减少与行锁定相关的消息流量,PolarDBMP 将行锁定信息直接嵌入到每行中,并且仅在 Lock Fusion 上维护等待关系。对于每一行,一个附加字段指示锁定事务的 ID。当事务尝试锁定行时,它只需将其全局事务 ID(如第 4.1 节中介绍的)写入此字段。如果行的事务 ID 字段已被活动事务占用,则检测到冲突,当前事务必须等待。在 PolarDB-MP 中,当尝试更新行时,它必须已经在包含该行的页面上持有 X PLock 锁。因此只有一个事务有权写入某一行的锁定信息,并且只有一个事务可以成功锁定该行。与单节点数据库类似,RLock 协议也遵循两阶段锁定策略,即锁定将一直保持到事务提交为止。

在 RLock 设计中,确定某一行是否被另一个事务锁定涉及检查事务的活动状态。 在多主数据库中,确定给定的全局事务 ID 是否代表活动事务可能具有挑战性。 但是,PolarDB-MP 的 TIT 设计有效地促进了这一过程。使用结合了 node_id、 本地 trx_id、slot_id 和版本的全局事务 ID,一个节点可以从另一个节点上的本地 TIT 或远程 TIT 检索事务的 CTS。如果此 CTS 填充了有效值,或者相​​应的 TIT 槽已被重用,则表示事务已提交且不再处于活动状态。相反,如果 CTS 字段保持其默认值,则该事务仍被视为活动。为了最大限度地减少过多的远程内存访问,PolarDBMP 在后台跨节点同步全局最小活动事务 ID。因此,如果事务的全局 ID 小于此全局最小活动事务 ID,则该事务不再处于活动状态。这种方法简化了锁检查过程,并提高了多主数据库中事务管理的效率。

行中的事务 ID 用作锁定指示器。因此,此协议仅支持排他 (X) 锁。PolarDB-MP 不支持行上的共享 (S) 锁,但可以接受。在 PolarDBMP 或许多其他数据库(例如 MySQL 变体)中,大多数读取请求都是快照读取,通过多版本并发控制 (MVCC) 处理,因此不需要锁。读取行时,事务会获取最新版本而无需行锁,但页面必须通过 PLock 进行 S 锁定。然后,事务根据其事务 ID 验证版本的可见性。如果版本不可见,事务将使用撤消日志重建可见版本,而页面上的 S 锁会阻止其他线程在此过程中修改行。因此,丢弃 S 类型的行锁不会影响读取请求的处理。在极少数情况下,需要对记录进行 X 锁定。 PolarDB-MP会将S锁升级为X锁,一般来说这种设计对性能影响不大,但节省了大量的消息传递。

锁定处理

我们在图 6 中演示了 RLock 处理。考虑这样一个场景:节点 2 上的事务 T30 尝试以独占方式锁定(X 锁定)某一行。它通过检查行的元数据发现该行已被另一个事务 (T10) X 锁定。作为响应,T30 首先远程调整远程事务 (T10) 元数据中的引用 (‘ref’) 字段。此操作表示有一个等待事务 (T30) 等待 T10 释放锁定。然后 T30 与 Lock Fusion 通信,发送有关其等待状态的信息。此信息将添加到 Lock Fusion 中的等待信息表中。一旦 T10 完成其事务并提交,它就会检查其“ref”字段。发现另一个事务 (T30) 正在等待,T10 通知 Lock Fusion 它已提交。 Lock Fusion 在收到 T10 的通知后,查阅等待信息表,然后通知 T30,T30 现在可以唤醒并继续其进程。

日志排序和恢复

日志记录方案

PolarDB-MP 采用 ARIES 风格的 [30] 日志记录技术,利用重做(预写)日志进行数据恢复,利用撤消日志回滚未提交的更改。在 PolarDB-MP 中,每个节点都维护自己的重做日志和撤消日志文件集。这种设计使不同的节点能够同时将这些日志同步到存储,而无需显式并发控制机制。PolarDBMP 中重做日志的持久性策略与传统解决方案保持一致。具体而言,在提交事务之前,相应的重做日志会同步到存储系统。这确保了已提交更改的持久性。在节点内,重做日志中的每个日志条目在生成时都会被分配一个唯一的增量日志序列号 (LSN)。重要的是,这个 LSN 还用作重做日志文件中的偏移量。因此,重做日志的持久性顺序反映了它们在节点内的生成顺序。

LLSN

然而,在 PolarDB-MP 中,一个显著的挑战来自于不同的节点可以独立更新同一页面,从而导致跨这些节点生成重做日志。这种情况导致多个节点对同一页面有不同的重做日志。由于每个重做日志都记录对特定页面所做的更改,因此在这种情况下恢复的关键是按照生成顺序应用同一页面的重做日志,而不同页面的日志可以按任何顺序应用。这种理解导致人们意识到,没有必要为所有重做日志维护一个总顺序;相反,只需要确保与同一页面相对应的日志的排序

为了应对这一挑战,PolarDB-MP 引入了逻辑日志序列号 (LLSN),为来自不同节点的日志建立了偏序,具体确保与同一页面相关的日志保持其生成顺序。但是,LLSN 不会对来自不同页面的日志施加任何特定的顺序,这也是不必要的。为了实现这一点,每个节点都维护一个节点本地 LLSN,该 LLSN 会随着每次日志生成而自动递增。当节点更新页面并生成日志时,新的 LLSN 会记录在页面元数据中,并分配给相应的日志。如果节点从存储或 DBP 读取页面,它会更新其本地 LLSN 以匹配访问页面的 LLSN,前提是页面的 LLSN 超过节点的当前 LLSN。这确保节点的 LLSN 与其访问的页面保持同步。随后,当节点更新页面并生成日志时,其 LLSN 会增加,从而保证新的 LLSN 大于之前更新页面的任何节点的 LLSN。得益于 PLock 设计,一次只能有一个事务更新一个页面。因此,当页面在不同节点上按顺序更新时,LLSN 会有效地按照日志的生成顺序进行维护。此外,日志必须按照生成的顺序进行持久化。在一个节点内,通过按 LSN 顺序持久化日志来保证这一点。当页面在两个节点之间更新时,一个节点会将其更新的页面推送到 DBP,然后再释放 PLock,从而允许下一个节点从 DBP 中检索它。PolarDB-MP 强制节点在将页面推送到 DBP 之前持久化与页面相关的所有日志。这确保了当节点更新页面时,其他节点生成的与该页面相关的先前日志已经存储到存储中。因此,与同一页面相关的日志在生成时会保持持久排序,从而保持跨多个节点的数据一致性。

恢复

在恢复过程中,应用重做日志来准确恢复已修改的数据页至关重要。如前所述,正确的数据库恢复依赖于按照 LLSN 的顺序应用重做日志。虽然单个日志文件中的 LLSN 始终是增量的,但当考虑不同节点上的不同日志文件时,它们可能会重叠。恢复期间的要求是按照 LLSN 顺序应用属于同一页面的日志。最简单的方法是加载所有日志文件并根据其 LLSN 对日志条目进行排序,但这需要太多内存来保存所有日志数据,并且在大量数据排序期间浪费 CPU 周期。为了避免这种情况,PolarDB-MP 每次只读取一小部分日志,然后应用它们。每次它只从每个文件中读取一个数据块,并确定这些数据块中的有界 LLSN(𝐿𝐿𝑆𝑁𝑏𝑜𝑢𝑛𝑑)。𝐿𝐿𝑆𝑁𝑏𝑜𝑢𝑛𝑑 可以保证文件中所有剩余日志的 LLSN 都大于 𝐿𝐿𝑆𝑁𝑏𝑜𝑢𝑛𝑑 。PolarDB-MP 只挑选这些数据块中 LLSN 小于 𝐿𝐿𝑆𝑁𝑏𝑜𝑢𝑛𝑑 的日志数据,并为应用程序解析它们。 LLSN 大于 𝐿𝐿𝑆𝑁𝑏𝑜𝑢𝑛𝑑 的其他日志数据将留到下一批。

撤消日志也受其重做日志的保护,在应用所有重做日志后恢复。在 PolarDB-MP 中,分布式事务是不必要的,因为事务可以在单个节点上执行。因此,我们可以像单节点数据库一样对未提交的事务执行回滚,直接应用撤消日志。

本文由作者按照 CC BY 4.0 进行授权

© xzxz. 保留部分权利。

本站采用 Jekyll 主题 Chirpy