pgrac 在 PostgreSQL 的进程模型之上叠加了一套完整的集群常驻进程体系。postmaster fork 机制保持不变,BackendType enum 在原有基础上追加 16 个新值;稳态下一个 4 节点主节点集群每节点运行约 26 个进程——10 个 PG 原生进程(其中若干做了针对性调整)加上 16 个 pgrac 新增 cluster daemon。
理解进程树是排查集群问题的第一步:pg_stat_activity 的 backend_type 字段直接映射到 BackendType enum,ps aux 输出中的 postgres: lms 0、postgres: lmd、postgres: cssd 等名称与设计文档一一对应。本章建立进程树的全景认知——每个进程是什么、为什么独立存在、以什么顺序启动和关闭、通过什么机制通信。进程内部的协议细节(GES 消息编码、PCM 状态机转换、RDMA QP 管理)留给深度页;本章只建立结构性词汇表。
本章引用的进程清单、aux 编号、SQLSTATE 与 GUC 默认值均来自 Stage 2 spec 原文(spec-2.5 CSSD / spec-2.6 qvotec / spec-2.18 LMS / spec-2.19 LMD / spec-2.20 LockAcquire S1–S7 等)。旧版草稿中出现过的 LMHB(Lock Manager HeartBeat)与 GRD0 daemon 不属于 pgrac 概念,本章不使用——心跳职责由 CSSD(aux #5)承担,GRD 状态由 LMS / LMD / LMON 协同维护,不存在独立的 GRD daemon。
postmaster 是整棵进程树的根。它在共享内存和锁结构初始化完成后,按照 4 个 Phase 依次 fork 所有子进程;子进程之间通过共享内存、信号和节点间 Interconnect 通信,不存在父子进程的直接依赖链(postmaster 是监控者,不是中间路由)。
postmaster
│
┌───────────────┴───────────────┐
│ │
PG natives pgrac new
─────────── ──────────────
walwriter (+BOC) LMS × N (default 4)
bgwriter LMD
autovacuum LCK
checkpointer LMON
archiver CSSD
logical_repl qvotec
... Interconnect Listener
Undo Cleaner / TT GC
DIAG / Cluster Stats
Sinval Broadcaster
Recovery Coord / Worker
MRP
图中"PG natives"一侧有若干进程做了 pgrac 专项调整(见 §8.2),并非完全保持原始行为。"pgrac new"一侧均为全新进程,在 PG 原生 BackendType enum 之外追加,不破坏任何既有 ABI。
稳态进程数估算:4 个 LMS worker + 1 LMD + 1 LCK + 1 LMON + 1 CSSD + 1 qvotec + 1 Interconnect Listener + 1 Undo Cleaner + 1 TT GC + 1 DIAG + 1 Cluster Stats + 1 Sinval Broadcaster = 15 个 pgrac 必启动进程;加上约 10 个 PG 原生进程,合计约 25–26 进程 / 节点(primary 稳态)。Recovery Coordinator / Recovery Worker / MRP 不计入稳态。
Recovery Coordinator、Recovery Worker(动态数量)和 MRP 不计入稳态:Recovery Coordinator / Worker 仅在 reconfig 期间按需 fork,完成后退出;MRP 仅在 standby + ADG 模式下启动。CSSD(aux #5)取代了旧设计中的独立 Heartbeat 进程:CSSD 既负责声明 peer 三态(ALIVE / SUSPECTED / DEAD),也负责广播 application-level heartbeat envelope,详见 §8.3 与 Chapter 5 §5.3.1。
PG 原生进程全部保留,部分做了针对性扩展。扩展原则:能在原有进程内追加逻辑的,不新增进程(BOC 嵌入 walwriter 就是典型);扩展只在集群模式生效,单机部署路径不变。
| PG 原生进程 | 调整内容 | 备注 |
|---|---|---|
postmaster | 启动时初始化 GES client;注册到 GRD;监控所有 pgrac 进程;进程死亡时做 restart / instance crash 决策 | 核心监控者 |
walwriter | 内嵌 BOC:100 μs 周期 flush;SCN piggyback 维护;per-thread WAL stream 处理 | 最重要的调整,见下注 |
bgwriter | 写脏块前 PCM 状态 check:仅 X 模式可 flush,非 X 模式跳过(让 PCM master 协调) | 防止跨节点 buffer 冲突 |
checkpointer | 跨节点 barrier checkpoint;触发 cluster checkpoint barrier | 关联 #18 |
archiver | per-thread WAL 归档;thread_id 隔离归档路径;归档完成上报 GRD | 关联 AD-009 |
autovacuum launcher | xid wraparound 计算感知 per-instance xid 分段(AD-012 例外 10) | 逻辑不变,边界感知 |
startup | 仅一次性 recovery:crash recovery 入口;检测 merged recovery 需求;触发 Recovery Coordinator;完成后退出。不再承担 standby 持续 apply | 持续 apply 交给 MRP |
walsender | 保留,无调整 | — |
walreceiver | ADG 下 per-thread 接收 | — |
logger | 保留,无调整 | — |
logical rep launcher / worker | 保留,无调整 | — |
walwriter 内嵌 BOC 是 §3.2 SCN 一章中描述的 "BOC flush" 的实现宿主。BOC 触发频率高(每 100 μs 一次)但单次工作量小,独立进程开销超过收益;BOC 与 WAL 落盘时序强耦合(commit 后 BOC 推进 SCN),内嵌保持了时序一致性。Oracle 的 BOC 也是 LGWR 内嵌职责,设计上保持对齐。
startup process 的重要调整:PG 原生 startup 在 standby 模式下会持续 apply WAL 直到 promote——这一行为在 pgrac 中移交给 MRP。pgrac 的 startup 只做启动期 crash recovery,完成后退出,职责单一、便于故障隔离。
pgrac 新增 16 类后台进程,按子系统分为 5 组。以下表格给出每个进程的名称、aux 编号、稳态数量和一句话职责;详细设计见 §8.5 IPC 模型和各功能深度页。
| # | 子系统 | 进程(aux #) | 稳态数量 | 一句话职责 |
|---|---|---|---|---|
| 1 | 锁与缓存 | LMS (Lock Master Service, aux #7, spec-2.18) | N=4(默认,GUC cluster.lms_workers 可调 1~16) | 处理跨节点 PCM/GES 远程请求,响应 buffer ship,执行锁授予/撤销决策,并附带 SCN piggyback。 |
| 2 | 锁与缓存 | LMD (Lock Manager Daemon, aux #8, spec-2.19) | 1 | 消费本节点 enqueue 的 wait-for graph,运行 Tarjan SCC 死锁检测,选受害者并通过 PROCSIG 取消。 |
| 3 | 锁与缓存 | LCK (Lock Process) | 1 | 持有 instance-level 锁(dictionary lock、cluster catalog lock),避免 LMS worker 被 instance 锁长期阻塞。 |
| 4 | 锁与缓存 | LMON (Lock Monitor) | 1 | 监控集群节点状态,协调 Reconfiguration(spec-2.29 coordinator tick),触发 GRD 重建与 fence 决策。 |
| 5 | 集群通信 | CSSD (Cluster Sync Service Daemon, aux #5, spec-2.5) | 1 | 声明每个 declared peer 的 ALIVE / SUSPECTED / DEAD 三态;每 1 秒广播 application-level heartbeat envelope;填充 pg_cluster_cssd_peers。 |
| 6 | 集群通信 | qvotec (Quorum Voting Coordinator, aux #6, spec-2.6) | 1 | 仲裁 voting disk quorum;定期轮询 disk slot,维护 cluster_qvotec_in_quorum() 谓词与 lease;填充 pg_cluster_quorum_state / pg_cluster_voting_disks。 |
| 7 | 集群通信 | Interconnect Listener | 1 | 监听 RDMA QP / TCP fallback 端口,接收消息并分发到 LMS / LMD / LCK 等 worker queue。 |
| 8 | Undo / TT | Undo Cleaner | 1 | 每 30 秒扫描本实例 undo segments,回收 RECYCLABLE 空间,维护 retention 窗口,推进 WRAP counter。 |
| 9 | Undo / TT | TT GC (Transaction Table GC) | 1 | 每 10 秒扫描 TT slot,回收 commit_scn 已被全集群 oldest_active_scn 超过的 expired slot 供新事务复用。 |
| 10 | 可观测 | DIAG | 1 | 跨节点诊断快照:检测 long-wait(默认 60 秒)触发 hang dump,接收其他节点的诊断请求,聚合 cluster log。 |
| 11 | 可观测 | Cluster Stats | 1 | 每 10 秒采样集群指标,填充 pg_stat_cluster_wait_events / pg_stat_cluster_wait_events_history(默认保留 7 天)等 sampling 视图。 |
| 12 | 可观测 | Sinval Broadcaster | 1 | 批量广播本节点 catcache / relcache invalidation 消息到所有其他节点,并注入对端 sinval queue,保持 catalog 一致性。 |
| 13 | 集中恢复 | Recovery Coordinator | 1(仅 reconfig) | 收集死亡节点 WAL,协调 k-way SCN merge,分配 Recovery Worker,协调 PCM lock 状态恢复,完成后退出。 |
| 14 | 集中恢复 | Recovery Worker | M 动态(仅 reconfig) | 接收 Coordinator 分配的 WAL 段,执行 redo / undo apply,上报进度,完成后退出。 |
| 15 | 集中恢复 | MRP (Managed Recovery Process) | 1(仅 standby + ADG) | 持续接收 walreceiver 的 per-thread WAL stream,集中 apply(对齐 Oracle MRP 模型),推进 apply_scn,直到 promote 退出。 |
| 16 | — | (保留位) | — | BackendType enum 末尾的预留位,供 Stage 3+ 新增 daemon 使用而不破坏 ABI。 |
视图命名规范:pg_cluster_*(无 stat_)用于状态/注册型视图——pg_cluster_nodes / pg_cluster_cssd_peers / pg_cluster_quorum_state / pg_cluster_voting_disks / pg_cluster_fence_state / pg_cluster_reconfig_state;pg_stat_cluster_* 用于采样/性能型视图——pg_stat_cluster_wait_events / pg_stat_cluster_wait_events_history / pg_stat_cluster_workers。两者命名差异反映了"快照状态 vs 累积统计"的语义边界,运维侧 SQL 不应混淆。
Sinval Broadcaster 是关键安全进程:crash 后 postmaster 立即 restart,超过 3 次 → instance crash。catcache / relcache 不一致是数据正确性问题,不是性能问题,不可降级。CSSD 与 qvotec 同属关键进程类,crash 后同样走 fail-closed 升级。
BackendType enum 扩展:新进程对应 miscadmin.h 末尾追加的 enum 值(B_CLUSTER_STATS / B_CSSD / B_DIAG / B_INTERCONNECT / B_LCK / B_LMD / B_LMON / B_LMS_WORKER / B_MRP / B_QVOTEC / B_RECOVERY_COORD / B_RECOVERY_WORKER / B_SINVAL_BCAST / B_TT_GC / B_UNDO_CLEANER),追加到现有枚举末尾,不改变既有值,保持 PG 16.13 ABI 兼容。pg_stat_activity 的 backend_type 字段自动展示,视图层无需修改。
LMS(Lock Manager Server,aux #7,spec-2.18 §1.4)与 LMD(Lock Manager Daemon,aux #8,spec-2.19)是 pgrac 全局锁子系统的两个 postmaster-forked 守护进程(spec-2.18 Q5)。它们的存在把"caller 同步等待 + LMON tick 兼职授予"这一旧实现,迁移到"caller 异步入队 + 专职守护进程串行决策"的稳定形态——所有权单一、fail-closed,无运行时回退路径。
两个 daemon 各自从临时宿主接管了一段关键路径:
lms_state 推进到 LMS_READY 后,LMON tick 中原本的 grant-decision 分支立即提前返回(early return);从该 tick 起,所有 inbound 的 GES 请求都由 LMS 拥有的 work_queue 消费,同一时刻只能有一个所有者(D1)。两条迁移都是 单一所有权 + fail-closed:若 cluster.lms_enabled = on 但 LMS 未就绪(fork 失败、heartbeat 超时、shmem 未挂载),caller 端 LockAcquireExtended 在 S1 直接 ereport(ERROR, '53R80', 'cluster_lms_unavailable')(spec-2.18 Q12),不回退到 LMON tick 路径。LMD 同理:未就绪时 wait-for graph 写入端拿到 53R81 cluster_lmd_unavailable(spec-2.19 Q12)。这两条 SQLSTATE 与对应的两个 PGC_POSTMASTER GUC cluster.lms_enabled(spec-2.18 Q10)/ cluster.lmd_enabled(spec-2.19 Q10)一一对应;修改需重启实例。
spec-2.20 激活后,caller-side LockAcquireExtended 在 should-globalize 路径上展开为七步状态机。这七步不是 daemon 内部的状态转换,而是 caller 在等待 LMS 决策期间穿过的代码区段;每一步都对应特定的锁分区 LWLock 持有/释放、PROCLOCK 表项变更和与 LMS 的消息往返。
| Step | 名称 | 关键动作 |
|---|---|---|
| S1 | should_globalize gate | 入口检查;若资源类不需跨节点 / LMS 未就绪 / fast-path 命中 → 早退或抛 53R80。 |
| S2 | LOCALLOCK 重入 | 通过 GrantLockLocal(locallock, owner) 完成重入并 early return;不可裸 ++nLocks,否则破坏 owner 记账。 |
| S3 | partition LWLock + PROCLOCK reservation | 获取锁分区 LWLock;在 PROCLOCK 表项上做 reservation(占位),不调用 GrantLock;随后释放 partition LWLock。 |
| S4 | 异步 enqueue + 等待 | 异步投递 GES_REQUEST 到 LMS work_queue;caller WaitLatch 等 LMS 回调,受 timeout 约束。 |
| S5 | GRANT 回调处理 | 重新获取 partition LWLock 并 recheck conflict。成功 → GrantLock 并置 grd_registered = true。仍冲突 → 在 PG_TRY() 内 WaitOnLock。失败 → 回滚 GES_RELEASE。 |
| S6 | release | 正常释放路径,对称回退 GES 远端持有。 |
| S7 | cleanup | 处理 REJECT / TIMEOUT / cancel:清除 reservation、回收资源、必要时 GES_RELEASE。 |
关键不变量(spec-2.16 L232 / L242):
GrantLock;两步之间 partition LWLock 已释放,GES 等待循环从不在持有 partition LWLock 时进行。若把 GrantLock 提前到 S3,等于在持锁期间发起跨节点等待,违反 LWLock 持锁不超过单次 CPU 抢占的核心约束。WaitOnLock 必须用 PG_TRY() / PG_CATCH() 包裹,cancel / SIGTERM 触发的 longjmp 会跳过正常清理路径;catch 块必须显式 GES_RELEASE 已 grant 的远端 holder,否则 GRD 上会留下幽灵持有者。这条与 I45 一起,定义了 S3–S5 区段的原子性回滚语义。spec-2.20 Q1 同时给出了第二层解读:S1–S7 在数字上对齐 Oracle DLM 经典的 7 个 lock 状态——STARTING / CONVERT / ACQUIRED / CONVERTING / RELEASED / CANCELED / COMPLETED。pgrac 在外部行为上选择与 Oracle 对齐(便于排错经验迁移),在内部命名上沿用 S1–S7 数字编号(便于 spec 交叉引用)。两套命名指向同一个状态机:caller-side S1–S7 是它在 PostgreSQL 进程中的代码段映射,Oracle 7 态是它在 DLM 协议层面的状态命名。
七步状态机的步骤分布在 caller backend、LMS daemon、LMD daemon 三方:
| 所有者 | 承担步骤 | 说明 |
|---|---|---|
| caller backend | S1 / S2 / S3 / S4 wait / S5 callback / S6 / S7 | 所有 partition LWLock 操作、PROCLOCK 表项变更、LOCALLOCK 记账均在 caller 上下文执行。 |
| LMS daemon | S4 work_queue 消费 + grant decision | LMS 拥有 inbound work_queue 消费者与 GES grant-decision body(spec-2.18 D1);决策结果通过 ProcSignal + latch 唤醒 caller。 |
| LMD daemon | 横向贯穿 S4–S5 | LMD 拥有 wait-for graph + Tarjan SCC + victim 选择(spec-2.19 D1,spec-2.22 D2–D3);命中环时通过 ProcSignal 取消选定的 caller,使其在 S5 的 WaitOnLock longjmp 出来并进入 S7 cleanup。 |
caller 与 LMS / LMD 之间的反向通道是 ProcSignal。两个新增 signal 都在 procsignal.h 早已预留:
PROCSIG_CLUSTER_GES_BAST(spec-2.17 Q8)— LMS 通知 caller "你需要降级或释放本地持有的远端锁"。Blocking AST 语义,caller 在 ProcessInterrupts 中处理。PROCSIG_CLUSTER_GES_CANCEL(spec-2.17 Q9)— LMD 在死锁检测命中环、或 caller 自身收到取消时使用;触发 S5 WaitOnLock 的 longjmp 出口。backend exit cleanup:spec-2.17 引入新的单点入口 cluster_grd_cleanup_on_backend_exit(procno),覆盖单 backend 的所有退出路径——客户端 CANCEL、外部 SIGTERM、on_proc_exit 回调链、self-abort(ereport(FATAL))。该入口显式排除 BAST timeout(spec-2.17 Q21):BAST timeout 走 reconfig / fence 路径,不是单 backend 退出,强行复用清理逻辑会错误地把整节点持有视为单 backend 持有并 GES_RELEASE。
禁止在 LMS / LMD 之间引入运行时回退(例如"LMS busy 时由 LMD 兼任 grant"或"LMD 失败时由 caller 同步检测")。单一所有权 + fail-closed 是 spec-2.18 / spec-2.19 的核心 invariant:任何回退都会让 grant-decision 或 deadlock-detection 同时出现两个所有者,导致 GRD 状态分叉或重复 victim 取消。运维侧若需禁用 LMS / LMD,唯一合法路径是修改 PGC_POSTMASTER GUC cluster.lms_enabled / cluster.lmd_enabled 并重启实例。
启动顺序体现了集群化依赖链:必须先有网络和心跳,才能启动锁服务;必须先有锁服务,才能进行 recovery;必须 recovery 完成,才能开始正常服务。
postmaster
│
├── Phase 0:基础
│ └─ logger(日志先行)
│
├── Phase 1:集群基础 ← 60 秒超时,失败 → instance crash
│ ├─ Interconnect Listener (网络层就绪)
│ ├─ CSSD (peer 三态 + heartbeat 广播)
│ ├─ qvotec (voting disk quorum 仲裁)
│ └─ LMON (加入集群 / GRD 同步)
│
├── Phase 2:锁服务 ← 30 秒超时,失败 → instance crash
│ ├─ LMS0..LMSn (并行 fork,→ LMS_READY 后 LMON tick early return)
│ ├─ LMD (wait-for graph + Tarjan SCC 接管)
│ └─ LCK
│
├── Phase 3:恢复(按需) ← 600 秒超时(GUC cluster.recovery_timeout)
│ ├─ startup process (crash recovery 入口)
│ │ ├─ 检测 merged recovery → LMON 启动 Recovery Coordinator
│ │ ├─ Recovery Coordinator → spawn Recovery Workers
│ │ └─ 完成后 startup / Coordinator / Workers 全部退出
│ └─ [standby + ADG only] MRP 启动
│
└── Phase 4:正常服务 ← 30 秒超时,单进程失败 restart 3 次
├─ checkpointer / bgwriter / walwriter (内嵌 BOC)
├─ archiver / autovacuum launcher
├─ TT GC / Undo Cleaner / Sinval Broadcaster
├─ DIAG / Cluster Stats / logical rep launcher
└─ 开始接受 client 连接
三条关键依赖 arrow:
关闭逆序:关闭顺序与启动逆向,关键不变量是"必须先释放全局锁(Phase 2 进程关),再断网(Phase 1 进程关)"——若顺序颠倒,其他节点无法感知锁释放,导致集群状态不一致。
关闭顺序:
1. 拒绝新连接
2. 等待 client backend 退出(默认 30s)
3. Phase 4 进程(Cluster Stats / DIAG / Sinval Broadcaster / Undo Cleaner / TT GC / archiver / autovacuum)
4. walwriter / bgwriter / checkpointer(final checkpoint)
5. [standby] MRP
6. Phase 2 进程(LCK / LMD / LMS0..LMSn)← 释放全局锁
7. Phase 1 进程(LMON / qvotec / CSSD / Interconnect Listener)← 通知 graceful leave
8. logger
9. postmaster 退出
pgrac 进程间通信分为两层:同节点进程依赖共享内存 + 信号 + 进程内 queue;跨节点进程依赖 Interconnect Listener 分发的消息队列。两层边界清晰,不存在"跨节点直接访问共享内存"的设计——跨节点数据访问全部经过协议消息(PCM block ship / GES lock grant)。
同节点 IPC:
| 方式 | 用途 |
|---|---|
| 共享内存(SysV / mmap) | 锁结构、buffer pool、TT slot、GRD cache |
| 信号(SIGTERM / SIGUSR1 / SIGUSR2) | postmaster → 子进程控制(与 PG 原生完全一致) |
ProcSignal(PROCSIG_CLUSTER_GES_BAST / PROCSIG_CLUSTER_GES_CANCEL / PROCSIG_CLUSTER_FREEZE_WRITES 等) | LMS / LMD / LMON → backend 的反向通道;在 ProcessInterrupts 中消费 |
Latch(SetLatch) | 唤醒等待的 backend / worker(复用 PG 机制) |
| 进程内 queue(lock-free ring buffer) | LMS dispatcher → LMS worker(见 §8.6.1) |
跨节点 IPC:
| 方式 | 用途 |
|---|---|
| Interconnect(RDMA / TCP) | 所有跨节点协议消息(PCM / GES / SCN / CSSD heartbeat) |
| Listener → worker queue | Interconnect Listener 接收入站消息后按 resource hash 分发 |
| Worker → Listener queue | 各 worker 将出站消息投递给 Listener 统一发送 |
LMS 是进程树中并发度最高的组件:N 个 worker(默认 4)共享一个 Interconnect Listener 入口,但每个 worker 处理独立的资源子集,不存在 worker 间的锁竞争。
分片策略:worker_id = hash(resource_id) % N。resource_id 对于 PCM 是 block 的 (tablespace_oid, relfilenode, block_no) 三元组,对于 GES 是锁资源名。同一资源始终由同一 worker 处理,避免并发 race。
消息流转:
跨节点消息到达
│
Interconnect Listener(单一入站点)
│
├─ 读取 msg.resource_id
├─ 计算 worker_id = hash(resource_id) % N
└─ 投递到 workers[worker_id].queue(lock-free ring buffer)
LMS worker 内部循环:
while (running):
msg = my_queue.recv() # 阻塞等待
handle_pcm_or_ges_msg(msg) # PCM 状态机 / GES grant
update_local_scn(msg.piggyback_scn) # Lamport 推进
if (reply needed):
outbound_queue.send(reply) # 交给 Listener 发出
这一设计的关键属性:Listener 是单线程扇出,worker 是单线程串行处理各自的队列——系统中不存在多个 writer 竞争同一 GRD 条目的场景(同一资源的所有消息串行到同一个 worker),RDMA write 操作也因此无需额外的 per-resource 锁。
N 的选择:默认 N=4 对应 4 节点 OLTP 集群 100K TPS 场景约 1.0–2.0 核 CPU 占用。N 过小时 worker queue 积压(监控 pg_stat_cluster_workers.queue_depth),N 过大时 LRU cache 分片效果下降(每个 worker 缓存的 GRD 条目减少)。生产调优建议参考 pg_stat_cluster_workers 视图的 queue_depth 字段。
故障决策原则:LMS / LMD / LCK / LMON / CSSD / qvotec / Interconnect Listener / Sinval Broadcaster 是"关键进程"——crash 后 postmaster restart,超过 3 次 → instance crash(被其他节点 fence)。Undo Cleaner / TT GC / DIAG / Cluster Stats 是"降级可恢复进程"——crash 后 restart,超过 3 次 → 仅警告,不 crash(GC 滞后或监控降级,但集群正确性不受影响)。
深度协议细节请参阅以下资源:
pg_stat_cluster_workers 视图字段、进程级故障决策表、GUC 参数完整列表(cluster.lms_workers / cluster.lms_enabled / cluster.lmd_enabled / cluster.recovery_timeout / cluster.cssd_heartbeat_interval_ms 等)background-process-design.md §2.1 / spec-2.5 / spec-2.6 / spec-2.16 / spec-2.17 / spec-2.18 / spec-2.19 / spec-2.20 / spec-2.22 — 16 类新增进程 + 7 类 PG 原生调整的完整规格、S1–S7 状态机不变量 I45 / I52、ProcSignal 反向通道、单点 backend exit cleanup 入口