Vadim Yatsenko:提交序列号(CSN)MVCC:无锁快照获取
内容提要
PolarDB在PostgreSQL 17上实现CSN MVCC,以单调递增的提交序列号替代活跃事务ID列表,使快照只需读取原子计数器而无需获取全局ProcArrayLock,可见性判断简化为一次整数比较,从而显著缓解高并发下的快照争用。移植过程中修复了内存屏障缺失、XID列表溢出误判、备库takenDuringRecovery未设置等正确性问题。CSN默认开启,可通过polar_csn_xid_snapshot保留XID列表模式。
延伸解读
快照获取从全局锁变为原子读
文章指出,PostgreSQL 传统快照需在 ProcArrayLock 下扫描 ProcArray 构建活跃事务 ID 列表,高并发时该锁成为吞吐瓶颈。PolarDB 的 CSN MVCC 改为读取原子计数器并存储单个快照 CSN,使快照获取不再获取全局锁。可见性判断也简化为一次整数比较,性能收益随并发度增长而放大。
默认开启,但保留 XID 列表兼容模式
CSN 默认启用(polar_csn_enable = on),对 DBA 而言最需理解的开关是 polar_csn_xid_snapshot。设为 true 时快照仍保留 XID 列表,但底层由 CSN 支撑,适用于依赖 XID 列表表示的工作负载或扩展。此外可选用 polar_csnlog_upperbound_enable 维护 CSNLOG 上界缓存,并观察共享内存命中/未命中计数器。
正确性修复集中在静默错误
移植后的修复并非崩溃类问题,而是可能返回错误结果的静默缺陷。例如缺少内存屏障可能导致快照“看到未来”;XID 列表溢出时未检查 suboverflowed 标志,可能把运行中事务误判为已结束;备库上 takenDuringRecovery 未设置,可能信任不可靠的 PD_ALL_VISIBLE 位而跳过逐元组可见性检查。这些修复确保无锁快照在主库和备库边界场景下仍正确。
Q&A
PolarDB 的 CSN MVCC 是怎么做到无锁获取快照的?
CSN MVCC 用单调递增的提交序列号(CSN)替代活跃事务 ID 列表。快照不再存储 XID 列表,而是存储一个快照 CSN。获取快照时,GetSnapshotDataCSN() 直接读取几个原子计数器(如 polar_next_csn、polar_latest_completed_xid、polar_oldest_active_xid),无需获取全局 ProcArrayLock,从而实现了无锁快照获取。
CSN MVCC 下如何判断一行数据对当前快照是否可见?
可见性判断简化为一次整数比较:如果写入该行的事务的提交 CSN 小于快照的 CSN,则该行可见。具体实现中,会通过 CSNLOG 查找事务的提交 CSN,然后与快照 CSN 比较,不再需要搜索 XID 列表。
polar_csn_xid_snapshot 参数有什么作用?
polar_csn_xid_snapshot 是一个配置参数。默认情况下,快照是纯 CSN 快照。当设置为 true 时,快照中会保留 XID 列表,但仍然以 CSN 为基础。这适用于依赖 XID 列表表示的工作负载或扩展。
CSN MVCC 在移植到 PostgreSQL 17 时遇到了哪些主要挑战?
移植到 PostgreSQL 17 时,需要适配上游代码的重大变化:RecentGlobalXmin 缓存被移除,改为每个后端计算 horizon 和 per-relation GlobalVis 机制;PGXACT 结构不再存放热事务字段,这些字段移到了 PGPROC 中;还需要适配 64 位 FullTransactionId 和新的表访问 API。
CSN MVCC 在正确性方面修复了哪些关键问题?
修复了多个正确性问题:1) 可见性快速路径缺少内存屏障,可能导致读到“未来”的快照;2) 当活跃事务列表溢出时,未检查 suboverflowed 标志,可能将运行中的事务误判为已结束,导致快照隔离被破坏;3) 备库上 takenDuringRecovery 字段未设置,导致错误信任全可见位;4) 其他如序列化、截断、回退逻辑等小修复。
对于 DBA 来说,CSN MVCC 默认是开启的吗?需要关注哪个参数?
CSN 默认是开启的(polar_csn_enable = on)。DBA 需要了解的关键参数是 polar_csn_xid_snapshot,它控制是否在快照中保留 XID 列表。此外,还可以通过 polar_csnlog_upperbound_enable 启用 CSNLOG 的上界缓存,以优化可见性热路径。