本章描述 pgrac 在 PG 原生 WAL 之上的升级方向,并按 Stage 标注实装状态。WAL 设计层面三条线索来自 AD-008(Lamport SCN 内嵌 commit/abort WAL record,xl_scn 可选字段)、AD-009(per-instance redo thread + 共享存储 WAL stream + k-way SCN merged replay)、AD-006(MVCC 引入 ITL / TT / Undo records 进 WAL)。三者收口于 Oracle Redo Records 的语义对齐:commit/abort record 携带 SCN、每 thread 内单调、多 thread 跨实例时按 SCN 合并回放。
Stage 1 已 ship(spec-1.18 + 1.19) 落地两件 WAL 实战:commit/abort WAL +8B 可选 xl_scn(gated by XACT_XINFO_HAS_SCN bit 9)、WAL page header 4B 字段占位(xlp_thread_id + xlp_cluster_flags,复用 MAXALIGN padding,page header on-disk 仍 24 B,不 bump catversion)。Stage 4(spec-4.1 — 4.13 规划) 真激活 per-instance WAL stream(pg_wal_threads/thread_NN/)、k-way SCN merge replay、新 RMGR(ITL / TT / Undo / PCM / BOC / Reconfig / Generic)、Cache Fusion Write-Ahead Rule、cluster crash recovery。本章按这一边界叙述:Stage 1 实装事实 + Stage 4 设计目标分开标注。
PG 原生 WAL 是单 stream + 单线程 LSN:所有 backend 共享同一个 WAL insert lock,recovery 单进程串行回放。AD-009 把这一模型扩展为 per-instance 独立 stream:每节点拥有自己的 pg_wal_threads/thread_NN/ 目录(WAL 设计文档 §4.2 / spec-1.19 §235;映射规则 thread_id = node_id + 1,保留 0 给 legacy sentinel),写入互不阻塞,recovery 时按 SCN 全局合并。Stage 1 当前实装仍是单一 pg_wal/ 目录 + 单 stream;per-instance WAL routing 推迟到 Stage 4.1(development-roadmap §4.3)。
| 维度 | PG 原生 | pgrac Stage 1 现状 | pgrac Stage 4 设计目标 |
|---|---|---|---|
| WAL 目录 | pg_wal/ | pg_wal/(未变) | pg_wal_threads/thread_NN/ |
| stream 数量 | 1 | 1 | 节点数(典型 2–8) |
| LSN 空间 | 单 LSN 序列 | 单 LSN 序列 | 每 stream 独立 LSN;全局顺序由 xl_xact_scn 决定 |
| WAL 文件可见性 | 仅本机 | 仅本机 | 共享存储,任意节点可读 |
| WAL record header (on-disk) | 24 B | 24 B(commit/abort 可选 +8 B xl_xact_scn,bit 9 flag gated) | 32 B unconditional(AD-008 二次扩展目标) |
| WAL page header (on-disk) | 24 B | 24 B(spec-1.19 复用 padding 加 xlp_thread_id 2 B + xlp_cluster_flags 2 B,不 bump catversion) | 同 Stage 1(spec-1.19 已永久占位) |
| Record 类型 | heap / btree / xact 等 | 原生类型(无新 RMGR ship) | + ITL / TT / Undo / PCM / BOC / Reconfig / Generic(7-8 个新 RMGR,WAL 设计文档 §6.1) |
| Recovery 模式 | 单进程串行 replay | 单进程串行 replay + xl_xact_scn observe 重建 SCN(spec-1.18 D7) | K-way SCN merge replay(Stage 4.5) |
| WAL 容量(DML-heavy) | baseline | baseline + commit/abort 各 +8 B(可忽略) | ~5.5× 估算(WAL 设计文档 §9.3,含 ITL + Undo + new RMGR) |
PG 原生 XLogRecord 固定 24 B:4 B 长度、4 B xid、8 B prev LSN、1 B info、1 B rmid、2 B padding、4 B CRC。这 24 B 不携带任何 SCN 或全局顺序信息——单 stream 单调 LSN 就够了,但多 stream 跨实例无法建立全局顺序。
Stage 1 实装(spec-1.18):record header on-disk 布局不变。xl_scn 作为 commit / abort record 的可选 8 字节字段实装:当 cluster.enabled=on 且 commit_scn 真分配时,XactLogCommitRecord / XactLogAbortRecord 设 XACT_XINFO_HAS_SCN(bit 9)flag + 在 xl_xact_origin 之后追加 8 B xl_xact_scn。XLOG_XACT_PREPARE 不携带 xl_scn(PREPARE 不是 durable commit point,spec-1.16 Q5);XLOG_XACT_COMMIT_PREPARED / XLOG_XACT_ABORT_PREPARED 携带(真 durable point)。Bootstrap mode 与 cluster.enabled=off 路径不写 HAS_SCN flag——这一路径与 vanilla PG 16 byte-identical,保 initdb / pg_upgrade 兼容。
WAL 设计文档 §5 描述的 ClusterXLogRecord(unconditional 32 B header,xl_scn 在所有 record 上)是 AD-008 第二次扩展的目标态,Stage 1 不实装——spec-1.18 选择更保守的 optional flag 路径,让 vanilla PG 兼容性与 SCN-aware crash recovery 共存。
Stage 1 实装(spec-1.19,WAL Page Header):在 XLogPageHeaderData 既有 MAXALIGN 4 B padding(offset 20)内复用,加入两个字段:xlp_thread_id(uint16,offset 20)+ xlp_cluster_flags(uint16,offset 22)。Page header on-disk size 保持 24 B 不变,不 bump catversion。Stage 1 阶段 xlp_thread_id 永久 hard-code 为 XLP_THREAD_ID_LEGACY = 0(legacy sentinel);Stage 2+ 真 per-instance routing 时按 thread_id = node_id + 1 映射(保留 0 给 legacy)。xlogreader.c 新加 validator hook 检查 Stage 1 invariant(thread_id 必须 LEGACY、flags 必须 RESERVED)。
PG / pgrac 共用 24 B record header (on-disk 不变):
+--------+--------+--------+--------+--------+--------+
| xl_tot_len (4) | xl_xid (4) | xl_prev (8) |
+--------+--------+--------+--------+--------+--------+
| xl_info | xl_rmid | (padding) | xl_crc (4) |
+--------+--------+--------+--------+--------+--------+
spec-1.18 commit/abort 可选扩展 (XACT_XINFO_HAS_SCN bit 9):
+--------+--------+--------+--------+
| xl_xact_scn (8 B) | ← 紧贴 xl_xact_origin 后
+--------+--------+--------+--------+
spec-1.19 WAL Page Header 复用 MAXALIGN padding (on-disk 仍 24 B):
+----+----+----+----+----+----+----+----+
| magic | info | tli | bytes 0-7
+----+----+----+----+----+----+----+----+
| pageaddr (8) | bytes 8-15
+----+----+----+----+----+----+----+----+
| rem_len (4) | tid (2) | flags (2) | bytes 16-23
+----+----+----+----+----+----+----+----+
↑ ↑
xlp_thread_id xlp_cluster_flags
(Stage 1: =0 legacy sentinel)
WAL insert critical section(spec-1.16 + 1.17):commit 路径 cluster_scn_advance_for_commit() 通过 lock-free pg_atomic_fetch_add_u64(spec-1.17 Q1,hot path 去 LWLock)拿到 commit_scn,再传给 XactLogCommitRecord。cluster_scn_observe() envelope verify 路径 + WAL replay observe 路径都走 lock-free CAS retry loop(spec-1.16 + spec-2.12 P1.1 内化)——禁止在该 hot path 加 LWLock。
本节列出的 7-8 个新 RMGR 来自 WAL 设计文档 §6.1(2026-04-25 v1.0),用以承载 ITL slot / TT slot / Undo record / PCM 转换 / BOC 记录 / Reconfig 事件等集群专属操作的 write-ahead。截至 Stage 2 收尾,这些 RMGR 没有任何一个在 linkdb 中实装。spec-1.20-1.22(TT slot typedef + undo segment header 占位)只 ship 了数据结构 typedef,未集成到 commit path、未引入对应 RMGR。新 RMGR 的真激活推迟到 Stage 3-4 对应 spec。本节按设计目标叙述,不代表当前 binary 已实现。
AD-006 设计目标:ITL slot、TT slot 和 undo record 三类集群专属操作必须写入 WAL——这是 write-ahead undo 正确性的基础。WAL 设计文档为此规划 7-8 个 RMGR,在 PG 原生 RMGR 框架上扩展、保持二进制兼容:
| RMGR | 用途 |
|---|---|
RM_CLUSTER_ITL_ID | ITL slot 写入 / cleanout / commit_scn 回写 |
RM_CLUSTER_TT_ID | TT slot 分配 / commit / abort / wrap 复用 |
RM_CLUSTER_UNDO_ID | Undo record 写入 / segment 状态变更 |
RM_CLUSTER_PCM_ID | PCM 锁转换(默认不写,仅 debug / reconfig 开启) |
RM_CLUSTER_BOC_ID | Broadcast on Commit SCN 广播记录 |
RM_CLUSTER_RECONFIG_ID | Reconfig Phase 切换 / Coordinator 选举 |
RM_CLUSTER_GEN_ID | 通用集群事件扩展点 |
典型 OLTP 事务(5 条 DML)的 WAL 量估算(WAL 设计文档 §9.3):PG 原生约 230 B / tx,pgrac 设计目标约 1,280–1,300 B / tx(5.5×)。增量主要来自 ITL write record(~54 B × 5)和 undo write record(~112 B × 5),header 增量仅占小头。这是设计目标投影,非实测数据。Stage 1 当前实测(spec-1.23 pgbench TPC-B baseline,27 combo)显示 cluster-ON 路径在 scale=100 c8 出现 -12% TPS overhead,候选 RCA 为 spec-1.17 BOC tick + spec-1.16 commit hot path + spec-1.18 xl_scn write;优化 defer 到 Stage 6 perf optimization。
ITL(Interested Transaction List)slot 每次写入、cleanout、commit_scn 回写都必须产生对应 WAL record,使 recovery 能精确重建 block 内的事务状态:
/* ITL slot 写入(DML 时随 heap record 一起产生)*/
typedef struct ItlWriteRecord {
BlockNumber target_block; /* 4 B 目标 block 地址 */
uint8 itl_slot_idx; /* 1 B slot 下标(0-based)*/
uint8 info; /* 1 B CREATE / UPDATE / CLEANUP */
ClusterItlSlotData slot_data; /* 48 B ITL slot 完整内容 */
/* total: ~54 B */
} ItlWriteRecord;
/* ITL cleanout(reader 触发延迟清理,同样必须走 WAL)*/
typedef struct ItlCleanoutRecord {
BlockNumber target_block; /* 4 B */
uint8 itl_slot_idx; /* 1 B */
SCN commit_scn; /* 8 B 回写到 ITL slot 的 commit_scn */
/* total: ~16 B */
} ItlCleanoutRecord;
ITL cleanout 是 reader 触发的脏写(将 commit_scn 回写进 block header),必须走 WAL,否则 recovery 无法区分 cleanout 前后的 block 状态。TT slot 的 alloc / commit / abort / reuse 同样各有对应 record,共同构成完整的事务状态链路。
Cache Fusion block transfer 协议(feature-019 / cache-fusion-protocol-design)规划在 Stage 5 真激活;本节描述的 gcs_wa.c 发送路径、XLogFlush(pi_lsn) 同步调用、batch fsync 优化均为设计目标。Stage 2 当前已 ship 的是 IC envelope 帧 + epoch enforce + Lamport piggyback + GES daemon skeleton(spec-2.4 / 2.13 / 2.18-2.23);3-way Cache Fusion 协议 + PI 链 + Write-Ahead Rule 推迟到 Stage 5。
Cache Fusion 传输脏 block 前,WAL 必须已 fsync——这是 feature-019(Write-ahead 全局版本)规定的核心约束,也是整个 Cache Fusion 持久性保证的基础:
单机 PG 规则:
脏 block 刷盘前 → 对应 WAL 必须已 fsync
pgrac / Cache Fusion 扩展规则:
脏 block 跨节点传输前 → 对应 WAL 必须已 fsync(含接收方继续修改的场景)
具体实现上,Cache Fusion 发送路径(gcs_wa.c)在打包 block 数据后、调用 Interconnect 发送前,强制同步调用 XLogFlush(pi_lsn),等待本地 WAL 持久化完成。批量优化(group commit-like)允许多个待传 block 共享一次 fsync,减少 I/O 次数。
Write-Ahead 是同步阻塞操作:XLogFlush 完成前,Cache Fusion 发送路径挂起等待。LGWR 卡住超时会触发 reconfiguration,不允许降级跳过 WAL 同步(正确性优先于可用性)。
WAL fsync 延迟是 Cache Fusion 端到端延迟的主要分量之一。NVMe 共享存储下典型 WAL fsync < 200 μs,远低于 Interconnect 传输的 ~5 μs RDMA 往返——因此整体延迟瓶颈通常不在 Write-Ahead,而在 GRD master 协调轮次数。
per-instance WAL routing(spec-4.1)、k-way SCN merge replay(spec-4.5)、Recovery Coordinator-MRP(spec-4.4)、5-subsystem crash recovery、online block / thread recovery、split-brain guard 等 13 个 Stage 4 spec 推迟到 Stage 4(development-roadmap §4.3)。Stage 1 当前 crash recovery 仍走 PG 原生单进程串行 replay 路径,仅在 commit/abort record 上通过 spec-1.18 D7 cluster_scn_recovery_replay_observe wrapper 重建 current_local_scn。本节描述 Stage 4 设计目标。
每个 pgrac 节点维护一个 thread-local WAL writer(LGWR),独立管理本节点的 pg_wal_threads/thread_NN/ 目录。WAL buffer 建议配置为 32 MB(vs PG 原生默认 16 MB),以容纳更多 record 类型的并发写入。xl_scn 单调不变式保证:本 thread 内 LSN 较大的 record,其 xl_scn 一定不小于 LSN 较小的 record。
崩溃后,存活节点(或新的 recovery 协调者)读取所有故障节点的 WAL stream,通过 K-way SCN merge replay 重建一致状态:
/* K-way merge — 优先队列按 xl_scn 升序 */
PriorityQueue pq; /* 最小堆,按 head_record.xl_scn 排序 */
for each thread t in merge_set:
t.head = read_next(t);
if (t.head) pq.push(t);
while (!pq.empty()) {
t = pq.pop_min(); /* 取全局 xl_scn 最小的 record */
dispatch_record(t.head);/* 按 block hash 分发 + redo apply */
t.head = read_next(t);
if (t.head) pq.push(t);
}
正确性依赖两个保证:per-thread xl_scn 单调(WAL insert lock 协议,§12.2);Cache Fusion serialization(同一 block 跨 thread 的 SCN 单调,Write-Ahead Rule,§12.4)。两者共同确保 merged order 与原始写入顺序一致。
Per-instance redo streams (各节点独立写):
Node 1: ●─●─●─●──────●── (thread A: SCN 42, 43; thread B: 44, 50; thread C: 61)
Node 2: ●─●─●────●─●───── (thread D: SCN 12; thread E: 44, 45; thread F: 55, 60)
Node 3: ●─●────────────── (thread G: SCN 8, 47)
↓ 按 SCN 排序合并 ↓
Merged: ●─●─●─●─●─●─●─●─●─● (replay order)
SCN: 8 12 42 43 44 45 47 50 55 60 61
Apply: 依序 redo each record。每条 record 仅 apply 到对应 block;
per-thread 顺序在 stream 内保留(thread_id 保证)。
Merged recovery 在集群 instance 故障(部分节点宕机)和 PITR 场景下均使用 K-way SCN merge。单机 crash recovery(仅一个 stream)退化为 PG 原生单线程 replay,增量仅为新 RMGR 的 redo handler(约 +5% 时间)。本算法 Stage 1 不实装;Stage 4.5 spec 真激活时通过本章 §12.2 描述的 spec-1.18 xl_xact_scn + spec-1.19 xlp_thread_id 占位作为输入数据格式。
深度设计细节与相关特性:
ClusterXLogRecord / ClusterXLogPageHeader 完整 C struct、7-8 个新 RMGR redo handler 实现、WAL insert critical section 伪代码、pg_cluster_wal_stats / pg_cluster_wal_scn_check 视图字段、WAL compression 扩展(lz4 2.1× ratio)xl_scn 语义(commit_scn vs local_scn)、Lamport 推进协议、BOC piggyback 更新路径、per-thread 单调不变式的形式证明pi_lsn 与 flushed_lsn 的关系约束、batch fsync 优化参数RM_CLUSTER_RECONFIG_ID records 在 Phase 切换时写入,recovery 重放后还原 Coordinator 状态