PolarDB PostgreSQL版(以下简称 PolarDB-PG)是一款阿里云自主研发的企业级数据库产品,采用计算存储分离架构,兼容 PostgreSQL 与 Oracle。PolarDB-PG 的存储与计算能力均可横向扩展,具有高可靠、高可用、弹性扩展等企业级数据库特性。同时,PolarDB-PG 具有大规模并行计算能力,可以应对 OLTP 与 OLAP 混合负载;还具有时空、向量、搜索、图谱等多模创新特性,可以满足企业对数据处理日新月异的新需求。
日志回放
延迟回放
LogIndex 机制下,RO 节点的 Startup 进程基于接收到的 WAL Meta 生成 LogIndex,同时将该 WAL Meta 对应的已存在于 Buffer Pool 中的页面标记为 Outdate 后即可推进回放位点,Startup 进程本身并不对日志进行回放,日志的回放操作交由背景回放进程及真正访问该页面的 Backend 进程进行,回放过程如下图所示,其中:
- 背景回放进程按照 WAL 顺序依次进行日志回放操作,根据要回放的 LSN 检索 LogIndex Memtable 及 LogIndex Table,获取该 LSN 修改的 Page List,若某个 Page 存在于 Buffer Pool 中则对其进行回放,否则直接跳过。背景回放进程按照 LSN 的顺序逐步推进 Buffer Pool 中的页面位点,避免单个 Page 需要回放的 LSN 数量堆积太多;
- Backend 进程则仅对其实际需要访问的 Page 进行回放,当 Backend 进程需要访问一个 Page 时,如果该 Page 在 Buffer Pool 中不存在,则将该 Page 读到 Buffer Pool 后进行回放;如果该 Page 已经在 Buffer Pool 中且标记为 outdate,则将该 Page 回放到最新。Backend 进程依据 Page TAG 对 LogIndex Memtable 及 LogIndex Table 进行检索,按序生成与该 Page 相关的 LSN List,基于 LSN List 从共享存储中读取完整的 WAL 日志来对该 Page 进行回放。
为降低回放时读取磁盘 WAL 日志带来的性能损耗,同时添加了 XLOG Buffer 用于缓存读取的 WAL 日志。如下图所示,原始方式下直接从磁盘上的 WAL Segment File 中读取 WAL 日志,添加 XLog Page Buffer 后,会先从 XLog Buffer 中读取,若所需 WAL 日志不在 XLog Buffer 中,则从磁盘上读取对应的 WAL Page 到 Buffer 中,然后再将其拷贝至 XLogReaderState 的 readBuf 中;若已在 Buffer 中,则直接将其拷贝至 XLogReaderState 的 readBuf 中,以此减少回放 WAL 日志时的 I/O 次数,从而进一步加速日志回放的速度。
Mini Transaction
与传统 share nothing 架构下的日志回放不同,LogIndex 机制下,Startup 进程解析 WAL Meta 生成 LogIndex 与 Backend 进程基于 LogIndex 对 Page 进行回放的操作是并行的,且各个 Backend 进程仅对其需要访问的 Page 进行回放。由于一条 XLog Record 可能会对多个 Page 进行修改,以索引分裂为例,其涉及对 Page_0、Page_1 的修改,且其对 Page_0 及 Page_1 的修改为一个原子操作,即修改要么全部可见,要么全部不可见。针对此,设计了 mini transaction 锁机制以保证 Backend 进程回放过程中内存数据结构的一致性。
如下图所示,无 mini transaction lock 时,Startup 进程对 WAL Meta 进行解析并按序将当前 LSN 插入到各个 Page 对应的 LSN List 中。若 Startup 进程完成对 Page_0 LSN List 的更新,但尚未完成对 Page_1 LSN List 的更新时,Backend_0 和 Backend_1 分别对 Page_0 及 Page_1 进行访问,Backend_0 和 Backend_1 分别基于 Page 对应的 LSN List 进行回放操作,Page_0 被回放至 LSN_N + 1 处,Page_1 被回放至 LSN_N 处,可见此时 Buffer Pool 中两个 Page 对应的版本并不一致,从而导致相应内存数据结构的不一致。
Mini transaction 锁机制下,对 Page_0 及 Page_1 LSN List 的更新被视为一个 mini transaction。Startup 进程更新 Page 对应的 LSN List 时,需先获取该 Page 的 mini transaction lock,如下先获取 Page_0 对应的 mtr lock,获取 Page mtr lock 的顺序与回放时的顺序保持一致,更新完 Page_0 及 Page_1 LSN List 后再释放 Page_0 对应的 mtr lock。Backend 进程基于 LogIndex 对特定 Page 进行回放时,若该 Page 对应在 Startup 进程仍处于一个 mini transaction 中,则同样需先获取该 Page 对应的 mtr lock 后再进行回放操作。故若 Startup 进程完成对 Page_0 LSN List 的更新,但尚未完成对 Page_1 LSN List 的更新时,Backend_0 和 Backend_1 分别对 Page_0 及 Page_1 进行访问,此时 Backend_0 需等待 LSN List 更新完毕并释放 Page_0 mtr lock 之后才可进行回放操作,而释放 Page_0 mtr lock 时 Page_1 的 LSN List 已完成更新,从而实现了内存数据结构的原子修改。
总结
PolarDB-PG 基于 RW 节点与 RO 节点共享存储这一特性,设计了 LogIndex 机制来加速 RO 节点的内存同步,降低 RO 节点与 RW 节点之间的延迟,确保了 RO 节点的一致性与可用性。本文对 LogIndex 的设计背景、基于 LogIndex 的 RO 内存同步架构及具体细节进行了分析。除了实现 RO 节点的内存同步,基于 LogIndex 机制还可实现 RO 节点的 Online Promote,可加速 RW 节点异常崩溃时,RO 节点提升为 RW 节点的速度,从而构建计算节点的高可用,实现服务的快速恢复。