GES(Global Enqueue Service)是 pgrac 的第二套跨节点锁协议,与 Cache Fusion 的 PCM 锁正交:PCM 管理 buffer cache 里的 block 级并发,GES 管理 buffer cache 之外的所有跨节点锁——行锁、表锁、事务锁、DDL 锁、advisory 锁、控制文件锁。两层协议共用同一套 GRD master 路由基础设施(见 grd-master),但服务完全不同的锁语义:PCM 用 N/S/X 三态,GES 保留 PG 原生的 8 种锁模式,以最小改动把单机锁语义扩展到集群范围。本章不涉及消息格式或字段细节,聚焦在概念层面:为什么需要 GES、它管理什么资源、与 PCM 的边界在哪里、master 如何路由、BAST 通知如何实现协作式释放,以及 PG 的 8 种锁模式在集群中如何保持不变。
PCM 锁针对 buffer cache 中 block 级别的并发访问而设计:8 KB block 是其操作粒度,N/S/X 三态加 has_pi 正交标志覆盖了 buffer 传输的全部场景。但数据库中大量的协调需求与 buffer cache 无关:
ALTER TABLE、DROP)需要阻塞所有节点的并发 DMLpg_advisory_lock 要求集群范围内互斥,而不是单节点互斥pg_control)的修改需要全局排他这些场景的共同特征是:锁的对象是逻辑资源(行、表、对象、Advisory key),而非物理 block。PCM 锁没有"表锁"或"行锁"的概念,它只知道 BufferTag(文件号 + 块号)。把行锁语义强行编码进 PCM 会导致语义混乱和粒度失控——这是 Oracle 设计中 GCS 与 GES 职责分离的根本原因,pgrac 遵循相同的架构决策。
边界清单:PCM 锁的对象是 BufferTag(物理 block),GES 锁的对象是 LockTag(逻辑资源)。两套锁协议在 GRD 中共享 master 路由表,但 keyspace 完全隔离——一个 block 的 PCM 锁记录与一个表的 GES 锁记录绝不会产生相互干扰。
pgrac GES 覆盖的锁类型来自 PG 原生 heavyweight lock manager 的所有 cluster-aware 子集,以及 pgrac 新增的专用 cluster lock 类型:
| 锁类型 | 缩写 | 用途 | 特性编号 |
|---|---|---|---|
| 事务行锁 | TX | 跨节点行级冲突检测(LOCKTAG_TUPLE / LOCKTAG_TRANSACTION) | #23 |
| 表级 DML 锁 | TM | DDL 与 DML 跨节点协调(LOCKTAG_RELATION) | #24 |
| 序列锁 | SEQ | 跨节点 sequence 分段分配 | #27 |
| 控制文件锁 | CF | pg_control 跨节点互斥写 | #77 |
| 用户锁 | UL | pg_advisory_lock 集群范围互斥 | #78 |
| 表空间 / 实例状态 / 调用 / 静默锁 | TT/IS/CI/XR | 四类集群管控 enqueue | #79 |
| PG 特有锁 | PG | SSI、SPECULATIVE TOKEN、VXID、FROZEN_ID | #124 |
不在 GES 管辖范围内的两类锁(出于 AD-011 决策不移植):LC Lock(Library Cache,Oracle 专属的 shared pool 元数据锁,PG 无对应概念)和 RC Lock(Row Cache,Oracle 数据字典缓存锁,PG 的 catalog 缓存由 relcache / catcache 管理,无需跨节点 row cache 锁)。
PCM 和 GES 职责的划分沿一条清晰的边界:buffer cache in / buffer cache out。下表从五个维度对比两套协议:
| 维度 | PCM | GES |
|---|---|---|
| 操作对象 | BufferTag(文件号 + 块号,物理 block) | LockTag(关系 OID、XID、对象 OID 等,逻辑资源) |
| 锁模式集合 | N / S / X(3 态)+ has_pi 正交标志 | AccessShare → AccessExclusive(PG 8 种模式) |
| 主要消息形态 | BLOCK_REQUEST / SEND_BLOCK / PI_PUBLISH | GES_LOCK_REQUEST / GES_BAST / GES_LOCK_GRANT |
| 访问频率 | 高频(OLTP 每 SQL 触发多次 block 访问) | 中频(事务粒度 / DDL 粒度,每事务触发若干次) |
| GRD keyspace | pcm_resource(block hash) | ges_resource(LockTag hash) |
两套协议唯一的共用设施是 GRD master 路由和 DRM(Dynamic Resource Mastering)——hash(key) % N 决定哪个节点是 master,DRM 可以把热点资源的 master 迁移到活跃节点以减少跨节点消息。
PCM 锁与 GES 锁的边界不是约定,而是语义上的不可替代:
PCM 锁的正确性依赖于 block 的物理地址唯一性——一个 BufferTag 唯一标识共享存储上的一个 8 KB block,PCM 状态机的不变量是"集群内持有 X 锁的节点最多一个",违反此不变量会导致 block 被两个节点同时修改,写入丢失。
GES 锁的语义是事务可见性——一个 LOCKTAG_TRANSACTION 锁代表"XID 是否提交",不涉及任何物理 block 的位置。若用 PCM 的 N/S/X 三态来表示行锁,那么 PCM 的"X→S downgrade"会被错误地解释为"行锁降级",事务隔离性将崩溃。
反过来,GES 不可能管理 buffer cache 里的 block 传输,因为 GES 的 BAST 是异步通知(等持有者事务自然结束),而 PCM 的 block transfer 需要同步协调(requester 必须等到 block 内容到达才能继续)。这两种时序语义不相容。
GES 的 master 选择与 PCM 完全一致:对每个 LockTag 计算哈希值,再对节点数取模,决定哪个节点是该资源的 master:
master_node = hash(LockTag) % N
master 节点负责维护该资源的两个核心数据结构:
grant_list:已被授予锁的(节点, 模式)列表;同一资源可以有多个节点同时在 grant_list 中(只要模式兼容,例如多个 AccessShare)。convert_queue:等待锁转换(或初次获取)的请求队列;按 FIFO + 优先级排列,避免饥饿。非 master 节点持有的 local_lockid 只是 master 视图的本地缓存——它减少了对 master 的消息往返(本地 fast path),但权威状态永远在 master 的 grant_list / convert_queue 中。
+-------+ +-------+ +-------+
resource ---> Master | Master | Master |
hash mod N Node 1 | Node 2 | Node 3 |
+-------+ +-------+ +-------+
hash%3=0 hash%3=1 hash%3=2
local_lockid (Node N) → forwards to → master(resource)
↓
grant_list + convert_queue
master 路由的 DRM 迁移机制(热点资源 master 动态迁移到活跃节点)与 PCM 共享同一套 GRD 协议,详见 grd-master。
BAST(Blocking AST,Blocking Asynchronous System Trap)是 GES 区别于传统阻塞锁最重要的设计。在传统单机锁管理器中,当节点 B 请求一个被节点 A 持有的冲突锁时,B 的请求被放入等待队列,B 进程被挂起——这是同步 wait。
GES 的 BAST 模式不让 requester 阻塞在等待队列中无事可做,而是:
GES_LOCK_CONVERT(或 GES_LOCK_REQUEST),Master 发现冲突,把请求记入 convert_queue。GES_BAST,通知它"有人需要这个锁,请在方便时释放"。GES_LOCK_RELEASE。convert_queue,授予节点 1 所请求的模式,发送 GES_LOCK_GRANT。Node 1 (requester) Node 2 (master) Node 3 (current holder)
| | |
|--- 1. CONVERT --->| |
| |--- 2. BAST ------->|
| | |
| |<-- 3. RELEASE -----|
| | on commit |
|<-- 4. GRANT -------| |
| |
BAST 的核心价值在于不打断持有者的正常事务执行。传统阻塞锁在持有方事务提交之前就会强制持有方进入等待状态(操作系统信号或 futex 唤醒),这在跨节点场景下意味着一次额外的网络往返进入 requester 的等待路径。BAST 把这个"请求信号"变成对持有者的异步通知——持有者在自己的事务生命周期内自然处理,不需要 requester 反复轮询,也不会产生无谓的上下文切换。
在无冲突路径下,GES 锁的 cross-instance 获取目标延迟约为 5 μs(Tier 1 RDMA);存在冲突但持有者事务即将提交的路径约为 10–20 μs;存在冲突且持有者事务运行中则等待时间由持有者事务决定(可能秒级)。
BAST 是协作式(cooperative)释放,不是抢占式。持有者在收到 BAST 后仍然可以继续执行当前事务直到提交,master 不会强制中断它。这意味着长事务会导致 requester 等待时间不可预期地长——长事务管理是使用 GES 的应用层必须关注的性能策略。
pgrac 有意不改变 PG 原生的 8 种锁模式,这是 AD-012 的核心决策之一。从 AccessShareLock 到 AccessExclusiveLock 的完整集合在集群中保持不变:
| 模式 | 典型触发 | 与其他模式冲突 |
|---|---|---|
| AccessShareLock | SELECT | 仅与 AccessExclusiveLock 冲突 |
| RowShareLock | SELECT FOR UPDATE | 与 ExclusiveLock、AccessExclusiveLock 冲突 |
| RowExclusiveLock | INSERT / UPDATE / DELETE | 与 Share 及以上冲突 |
| ShareUpdateExclusiveLock | VACUUM、ANALYZE | 与 ShareUpdateExclusive 及以上冲突 |
| ShareLock | CREATE INDEX(非并发) | 与 RowExclusive 及以上冲突 |
| ShareRowExclusiveLock | 触发器等 | 与 RowExclusive 及以上冲突 |
| ExclusiveLock | REFRESH MATERIALIZED VIEW CONCURRENTLY | 与 RowShare 及以上冲突 |
| AccessExclusiveLock | ALTER TABLE、DROP | 与所有模式冲突 |
pgrac 改变的只有两点:scope(从单节点扩展到集群范围)和 acquire path(本地 fast path 不变,跨节点场景通过 GES master 路由协调)。兼容性矩阵、锁升级语义、等待图(deadlock detection)与单机 PG 完全一致——这确保了现有的 PG 应用在迁移到 pgrac 集群时,锁行为不会产生意外差异。
本地 fast path 保持高效:当本节点的 LOCALLOCK 缓存命中(本节点已持有该锁的兼容模式),GES 不产生任何网络消息,延迟与单机 PG 相同。只有 LOCALLOCK miss 且资源是 cluster-aware 类型时,GES 才触发跨节点协议。
本章建立了 GES 的概念框架:PCM/GES 的边界由"buffer cache in / buffer cache out"决定(3.3.1 节说明了为什么边界不可交叉);master 路由通过 hash(LockTag) % N 把每个 enqueue resource 分片到唯一 master 节点,grant_list + convert_queue 是 master 维护的权威状态;BAST 模式实现协作式释放,让持有者在自己的事务生命周期内自然处理锁释放,而不是被强制中断;PG 的 8 种锁模式完整保留,scope 从单节点扩展到集群,acquire path 在 LOCALLOCK miss 时走 GES 协议。
以下深度页提供了协议实现层面的完整细节:
后续手册章节将在 GES 概念之上继续构建:Chapter 4 — SCN(Lamport 时钟,所有跨节点消息(包括 GES 和 PCM)都 piggyback 当前 SCN,用于因果排序和一致性读);Chapter 5 — Reconfiguration(节点拓扑变化时,GRD master 重新分配、grant_list 重建、孤立锁清理的完整状态重建流程)。