Cache Fusion 是 pgrac 最核心的跨节点机制:当节点 B 需要一个 block 而节点 A 的 buffer pool 已持有它时,pgrac 把 block 直接从 A 的内存传输到 B 的内存,完全绕过磁盘。这个看似简单的描述背后,隐藏了三个相互依赖的子系统:buffer 分类(谁存什么版本)、PCM 锁(谁有资格读写)、传输协议(怎么在节点间安全移动 block)。
本章建立理解 Cache Fusion 所需的概念框架——三类 buffer 的用途边界、PCM 锁的三态状态机、3-way 协议的参与方、以及跨节点写前日志约束——不涉及消息格式或字段细节,这些内容指向各自的深度页。章节顺序遵循依赖关系:从"为什么需要 Cache Fusion"(动机)→ "存什么"(buffer 类型)→ "谁控制访问权限"(PCM 锁)→ "如何传输"(3-way 协议)→ "如何保证持久性"(WAL 约束)→ "何时失效"(限制与规避)。
如果你已熟悉 Oracle RAC 的 GCS 层,本章的概念对你来说基本是 1:1 映射;pgrac 刻意与 Oracle Cache Fusion 语义对齐(N/S/X 状态名、PI 概念、3-way 路径),以降低专业运维工程师的学习成本。
共享磁盘集群的根本矛盾:多节点需要访问同一份数据,而这份数据同一时刻只能有一个"权威版本"在某个节点的内存中。传统做法是让请求节点把 block 刷回磁盘,再由请求方从磁盘读取——这在共享磁盘模型下是可行的,但代价高昂。
磁盘 I/O 的典型延迟在几毫秒量级(SSD ~0.1-1 ms,HDD ~5-10 ms)。与此对比,节点间 RDMA 互联的单次 block transfer 目标延迟为 5 μs——比磁盘快 20× 到 2000×。对于 OLTP 负载,一个典型的读事务需要访问几十个 block;若每个跨节点访问都要走磁盘,集群扩展效益几乎为零。
Cache Fusion 把跨节点 block 访问从磁盘 I/O 变成内存级传输,这是 share-disk 集群实现接近线性扩展的前提条件。没有 Cache Fusion,share-disk 集群就退化为"多节点争抢同一磁盘 I/O 带宽"的反模式。
pgrac 的 block transfer 建立在三层互联架构之上:
| Tier | 技术 | 单次 3-way 总延迟 | 典型场景 |
|---|---|---|---|
| Tier 1 | RDMA InfiniBand / RoCE + 硬件优化 | ~4-5 μs | 机架内高性能节点 |
| Tier 2 | RoCE(软件路径) | ~35 μs | 同数据中心跨机架 |
| Tier 3 | TCP/IP | ~300 μs | 跨数据中心 / 容灾 |
Tier 1 的 RDMA 实现允许 zero-copy block transfer(8 KB block 直接写入目标节点的 buffer pool 帧),CPU 开销约 2 μs(发送 + 接收双端合计),内存带宽消耗极低。
延迟目标汇总:Tier 1 RDMA 互联 3-way 总延迟 ~5 μs;Tier 2 RoCE ~35 μs;Tier 3 TCP ~300 μs。即便是最慢的 TCP 路径,仍比 SSD 随机读快 3-10×。
pgrac buffer pool 在 PG 原生单版本 buffer 之上引入三类 buffer,分别服务不同的访问模式。完整的数据结构设计参见 buffer-pool。
三类 buffer 的功能边界如下:
| 类型 | 角色 | 谁读 | 谁写 | 是否跨节点传输 |
|---|---|---|---|---|
| Current(XCUR/SCUR) | 最新版本;可修改 | 所有节点 | 持有 X 锁的节点 | 是(3-way / 2-way) |
| CR(Consistent Read) | 历史版本;只读 | 查询读取旧快照 | 由 Current 构造,不可修改 | 否(本地构造) |
| PI(Past Image) | 脏副本保留镜像 | 一致性读兜底 | X 锁让出时自动生成 | 否(本地保留) |
Current buffer 又细分为两个 PCM 锁模式:XCUR(独占,可写)和 SCUR(共享,只读)。这两个子模式对应 PCM 锁的 X 和 S 状态,在下一节详述。
Current buffer 在集群中有且只有一个"权威 current"副本(XCUR 或多节点共享 SCUR)。所有写操作必须在持有 X 锁的节点上进行,其他节点若要读取最新版本,需要先获取 S 锁或触发 3-way 协议拿到 block。
CR buffer 是在请求节点本地构造的只读副本:当节点需要读取某个历史快照版本,而当前 Current buffer 已被更新版本占用时,pgrac 用 Current + undo 信息回滚到目标 SCN,生成 CR buffer。CR buffer 完全本地,不需要跨节点传输。CR buffer 的 SCN 上限由发起查询的事务快照决定(快照隔离),构造完毕即可服务读请求,无需进入 GRD。
PI buffer 是三类 buffer 中最具 pgrac 特色的概念,也是理解 Cache Fusion 完整性所必需的。
PI 的产生:当持有 X 锁(即 XCUR)的节点 A 被要求把 block 传给节点 B 时,若该 block 是脏 block(已修改但尚未刷盘),A 在传出 block 之前必须保留一份副本——这就是 PI。PI 的存在让节点 A 的一致性读请求(针对 X 让出前的历史版本)仍然可以满足,而无需从磁盘重读。
PI 的注销:PI 在以下两种情况之一发生时注销:
注销之前,PI 必须保留在原节点的 buffer pool 中,不可被 eviction 替换,即使 buffer pool 内存压力较高。这是 buffer pool eviction 策略需要感知 PI 状态的原因(Current 可以驱逐,PI 在 WAL 未持久化时不可驱逐)。
PI 与 WAL 的耦合(feature-019 规则):脏 block 在跨节点传输前,对应的 WAL 记录必须 fsync。这确保接收方即使崩溃,也可以从日志恢复,而不依赖于传出方(传出方可能已经崩溃)。这个规则是第 2.5 节的主题。
PI 不是 CR buffer 的别名。CR 是"构造出来的历史版本",PI 是"传走前保留的脏副本"。两者生命周期完全不同。
PCM(Parallel Cache Management)锁是 Cache Fusion 的控制层:它跟踪每个 block 在集群范围内的访问权限状态,确保在任何时刻,block 的修改权限不会在两个节点间发生冲突。每个 block 在 GRD(Global Resource Directory)中有唯一的 PCM 锁记录,由该 block 的 master 节点管理。
GRD 是 pgrac 的全局资源元数据字典,按 resource_id(对于 block 即为 BufferTag 哈希)分片到各节点。每个 block 的 PCM 锁状态存储在对应 master 节点的 GRD 分片中:
GRD 的分片策略(哈希到哪个节点做 master)由集群初始化时配置,reconfig 事件后会重新分配(DRM,Dynamic Resource Mastering)。这意味着同一个 block 的 master 节点可能随集群拓扑变化而迁移,但在任何稳定时刻,master 唯一且明确。
PCM 锁有三个基本状态:
has_pi 正交标志:X 状态有一个正交标志 has_pi,表示"本节点在传出 X 锁时保留了 PI 副本"。这个标志不改变锁的授权语义,但影响 master 节点对一致性读的路由:若请求方需要旧版本,master 知道 PI 在哪个节点,可以直接路由。
三态 + has_pi 的组合覆盖了所有跨节点 buffer 一致性场景。
PCM 锁的生命周期与 block 在 buffer pool 中的存活绑定:当 block 被 eviction 驱逐时,持有 S 或 X 的节点必须先向 Master 发送 BLOCK_RELEASE 消息,释放 PCM 锁,再回收 buffer 帧。这个约束确保 Master 的 GRD 视图不会出现"实际 block 已被驱逐但 Master 仍认为持有者在缓存"的状态漂移。
完整的状态机转换细节(包括 BAST 通知路径)参见 pcm-lock。
+-----+ N→S +-----+
| N |---->| S |
+-----+ +-----+
| ↗ |
N→X| ↗ X→N |S→X
↓ ↗ ↓
+---------+
| X | (has_pi orthogonal)
+---------+
PCM 状态机的完整转换集合由三态的全排列(3×3 = 9 条有向边)构成:
| 转换 | 触发场景 | 谁发起 |
|---|---|---|
| N → S | 请求读取 block | Requester → Master |
| N → X | 请求写入 block(新页) | Requester → Master |
| S → X | 升级为独占写 | Requester → Master |
| S → N | 其他节点请求 X,本节点被撤销 S | Master → Holder |
| X → S | 降级:其他节点请求 S | Master → Holder |
| X → N | 完全失效:其他节点请求 X | Master → Holder |
| X → X | 所有权转移(不常见,reconfig 场景) | Master 协调 |
| S → S | 共享持有数量变化(新成员加入) | 无显式消息 |
| N → N | 无操作 | — |
S → X 升级需要 master 先向所有持有 S 锁的节点发送 INVALIDATE,等待全部确认后才授予 X。这是集群写入开销最高的路径,应用层应尽量避免"先 S 后 X"的访问模式。
3-way 是 Cache Fusion 的标准协议路径,命名来自它涉及三个参与方:Requester(需要 block 的节点)、Master(该 block 的 PCM 锁 master 节点,由 GRD 哈希决定)、Current Holder(当前持有 block 的节点)。三方在一次 block transfer 中扮演不同角色,通过两轮消息往返把脏 block 直接从 Holder 的内存传到 Requester 的内存。
协议的两个不变量贯穿所有路径:
Node B (req) Node M (master) Node A (holder)
| | |
|--- 1. CR_REQ ----->| |
| |--- 2. ASK_HOLDER ---->|
| | |
| |<-- 3. SEND_BLOCK -----|
|<--- 4. SEND -------| |
| |
| steady state: 0-way (cached locally) |
协议四步:
0-way 稳态:一旦 Node B 拿到 block 并进入本地缓存,后续访问同一 block 无需任何网络消息(PCM = S 或 X,直接本地读写)。这是 Cache Fusion 在"热 block"场景下接近零开销的原因。在实际 OLTP 工作负载中,大多数 block 访问发生在 0-way 路径;3-way 仅在首次获取或锁竞争时触发。
2-way 路径:当 Master 与 Holder 是同一节点时,步骤 2-3 合并,只需两次消息往返(~3 μs on Tier 1)。
协议的完整消息字段定义、X→S downgrade 时序、S→X upgrade 时序,参见 cache-fusion。
Cache Fusion 传输脏 block 的语义与单机的 WAL-before-page 规则不同,需要额外约束。
在单机 PG 中,脏 buffer 在被 eviction 前,对应的 WAL 必须先 fsync(write-ahead log 原则)。在集群中,这个约束需要扩展到跨节点传输场景:
feature-019 规则:脏 block 从持有方传向另一节点之前,该 block 上所有已提交事务的 WAL 记录必须已经 fsync 到本节点的 redo stream。
这一规则确保:若 Holder 节点在传出 block 后立即崩溃,接收方(Requester)仍然可以用 Holder 的 redo log 重建该 block——即便 Holder 不再可用。不满足此规则,就会出现"block 已在 Requester 使用,但 Holder 上对应的 WAL 从未持久化"的数据丢失窗口。
实践中,LMS(Lock Manager Server)进程在 block transfer 前会检查 block_lsn(block 最后修改对应的 LSN),确保该 LSN 已经被 WAL writer 持久化,再发出 block。这个检查对于非脏 block(未修改的 shared read)是免费路径,不增加任何 fsync 等待;仅脏 block 有可能触发 WAL writer 的同步等待。
从 PI 的角度看,feature-019 规则与 PI 生命周期紧密耦合:PI 可以在对应 WAL 已持久化后注销,正是因为"WAL 已 fsync = 磁盘有持久化版本 = PI 的兜底角色已被磁盘接管"。两个机制(WAL fsync 前置 + PI 保留)共同构成 Cache Fusion 正确性的双保险。
WAL 记录格式与 LSN 管理的详细设计参见 wal-record。
feature-019 规则是 Cache Fusion 正确性的关键约束。违反它(例如绕过 WAL fsync 直接传 block)会导致集群部分节点持有未持久化数据,节点崩溃后无法通过 redo 恢复。
Cache Fusion 在以下场景下效益有限,甚至可能成为性能瓶颈:
单行热 tuple(Hot Row Contention)
若一个高频更新的单行(例如计数器、流水号)被多个节点交替写入,该 block 的 PCM 锁会在 X 状态下不断在节点间迁移:A 写 → X 到 B → B 写 → X 回 A → A 写……每次迁移都触发完整的 3-way 协议。在 Tier 1 RDMA 下,每次 ~5 μs;在 Tier 3 TCP 下,每次 ~300 μs。写入 QPS 超过 10K/s 时,PCM 协议开销可能超过业务逻辑本身。
跨节点 Ping-Pong
若应用层负载均衡把同一行的读写随机分发到不同节点,block 的 PCM 状态会在 S 和 X 之间反复振荡(ping-pong)。每次 S→X 升级都需要 master 先 invalidate 所有 S 持有者——在持有节点数量多时,这是集群中开销最高的锁协议路径。
大量小事务跨节点竞争同一关系
OLTP 场景下,若同一张热表(如订单表、库存表)被所有节点并发写入,且写入的行分布在少数几个 block(低基数 block 布局),PCM 锁的 X→S→X 振荡会频繁触发 INVALIDATE 广播。这类问题在 Oracle RAC 生产环境中被称为 "gc buffer busy" 等待事件,pgrac 的等价监控指标是 pg_cluster_wait_events 中的 cf_buffer_busy。
应用层规避建议:
pg_cluster_sequence(per-instance 分段分配,批量预取,跨节点无竞争)。pg_cluster_cf_message_dist 视图观察 BLOCK_INVALIDATE 频率;高频 invalidate 的 block tag 是 ping-pong 热点的直接信号。Cache Fusion 的"不帮忙"场景本质上是应用访问模式问题,不是协议缺陷。Oracle RAC 生产部署中,最常见的性能问题几乎都来自应用层对 hot block 的无差别访问,而非 Cache Fusion 协议本身的瓶颈。识别这类问题的第一步是查看 pg_cluster_wait_events,重点关注 cf_buffer_busy 和 cf_cr_request 的等待时间分布。
本章建立了 Cache Fusion 的概念框架:三类 buffer 的用途边界(Current/CR/PI)、PCM 锁的三态状态机(N/S/X + has_pi 正交标志)、3-way 协议的三方参与方(Requester/Master/Holder),以及跨节点写前日志的正确性约束(feature-019 规则)。这个框架是理解 pgrac 集群行为的基础词汇;后续章节的所有机制都建立在这些概念之上。
下一章 Chapter 3 — GES Concepts 介绍 buffer 层之外的另一套跨节点锁协议:GES(Global Enqueue Service)负责 enqueue 锁(行锁、表锁、事务锁)的跨节点协调,与 PCM 锁在职责上完全正交,在架构上通过 GRD 共用 master 路由基础设施。理解 GES 需要先掌握本章的 PCM 状态机,因为 GES 的 BAST(Blocking AST)通知机制在结构上与 PCM 的 INVALIDATE 路径高度对称。
深入协议细节(消息格式、字段定义、时序图、性能预算)请阅读 Cache Fusion 深度页。