暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

KES V9R2C16实测:64 位事务 ID 之后,回卷预警不再出现

某天晚上十点多,一套业务库突然写不进去了。前端请求开始超时,待处理的消息一条条堆着,应用日志刷的全是插入失败。工程师查看磁盘和连接数,都没到上限。ksql 还能连,SELECT 也有结果,偏偏写入这条路断了。

ksql 一进去,age(datfrozenxid) 已经逼近回卷保护阈值。autovacuum 一直扫表,回收还是推不动。sys_stat_activity 里有个会话停在 idle in transaction,xact_start 还是下午。一个批处理程序调外部文件接口,接口挂了,事务没提交也没回滚。这个会话把 oldest xmin 钉住,死元组暂时不能移除。XID 照样涨,距离回卷保护阈值的余量越来越小。

这台库当时跑的是 KES V9R2C14,事务 ID 32 位。32 位 XID 能编码 2^32 个值,正常的前后比较窗口约 2^31,也就是 20 亿这个量级。判断回卷风险,要看当前 XID 与最老未冻结 XID 的事务年龄。年龄逼近回卷保护阈值时,系统会限制分配新 XID,免得把旧行看成未来事务。这是 32 位编号空间的约束。库没宕,业务已经停了。把这个会话 terminate,xmin 松开,补一轮 freeze,写入才恢复。

后来看到金仓 KES V9R2C16 的可用性说明,里面直接点了这件事。说明书(V009R002C016,2026 年 8 月 28 日)2.3.4:

支持 64 位事务 ID(XID),避免因 32 位事务 ID 回收不及时或长事务阻塞导致的业务中断问题。

于是我找了 C014 和 C016 两套库,查参数、看日志,再挂一个长事务试试清理。

验证环境

两套正式库用来读参数,临时实例分批建,都在同一台机器上,都是 Oracle 兼容模式:

实例 端口 用途
C014 正式库 54322 读版本和参数,未改配置、未重启
C016 正式库 54324 同上
C014 / C016 临时库 54325 / 54326 9 月 4 日,跳号实验
C014 / C016 临时库 54325 / 54326(重建) 9 月 5 日,长事务清理对照、预警复测

正式库版本信息:

项目 C014 C016
version() KingbaseES V009R002C014 KingbaseES V009R002C016
database_mode oracle oracle
Catalog 202602101 202608061

ksql 走各自 Server/bin、Server/lib,连 127.0.0.1,用户 system,库名 test。

我用 sys_resetwal -x 把 NextXID 调到高位,省去累积事务的过程。这次看到了预警差异,没有复现最终拒写。C014 的 initdb 不支持 --xid,两边都是 initdb 之后再 resetwal,起点才对得齐。跳号后两边都因 clog 对应段文件缺失起不来,补上 256KB 全零的 sys_xact 段文件才启动。这是人为跳号绕过了正常的 clog 分配,跟版本没关系,两边表现一样。这次用的是没有生产业务数据的临时实例,生产库不要照搬。临时库测完就删了。

同一个 NextXID,C014 报回卷预警,C016 没有

两边 resetwal 到 2146470000,txid_current() 都返回这个值。对齐的只是 NextXID 这个数值,两边的事务历史和冻结水位并不相同。

9 月 4 日连上 C014,日志里是:

WARNING:  database "kingbase" must be vacuumed within 1014764 transactions
HINT:  To avoid a database shutdown, execute a database-wide VACUUM in that database.

template1、template0、security 的余量同样是 1014764。HINT 里的 database shutdown 是预防性提示,库并没有停。算余量得带上对应数据库的冻结水位。9 月 5 日复测时,我查到 C014 的 datfrozenxid 在启动后发生了变化,只拿 2^31 - NextXID 算不准。

C016 没有出现 must be vacuumed,也没有 shutdown 提示。两边 INSERT 都正常。9 月 5 日复测又跑了一遍,告警差异一致,system 超级用户和普通测试用户都能写。

9 月 4 日还做过第二次 resetwal,把 NextXID 抬到 2147478000,两边各跑 8 个客户端、每客户端 2000 笔的 kbbench,共 16000 笔。跑完 txid_current 都是 2147494002,INSERT 仍然成功,没有复现拒写。

这 16000 笔没有把余量跑完。C014 第一次跳号后日志给的余量是 1014764,要走到拒写得继续往上造事务,这次没做。判断 C016 的编号空间,我靠的是另外三处:freeze 三个参数是 int64 类型,age(datfrozenxid) 变成 bigint,NextXID 没有 epoch 前缀。

长事务:旧快照不释放,两边都清不掉

9 月 5 日另建两套临时实例,这次不跳号,在正常事务编号下做清理对照。

向 review_dead 插 1000 行,执行 VACUUM FREEZE。会话 A 开一个 REPEATABLE READ 事务,读这张表并取到事务 ID,然后挂着不动,sys_stat_activity 里显示 idle in transaction。会话 B 把这 1000 行删掉并提交,再执行:

VACUUM (VERBOSE, FREEZE) review_dead;

两边都留下了 1000 个暂时不能清除的 dead row versions:

-- C014
DETAIL:  1000 dead row versions cannot be removed yet, oldest xmin: 1150
-- C016
DETAIL:  1000 dead row versions cannot be removed yet, oldest xmin: 1162

我结束会话 A 的事务,再跑 VACUUM,两边都清掉了这 1000 行。第一次 VACUUM 也推进了一部分冻结水位:C014 的 relfrozenxid 从 1149 到了 1150。受旧快照限制的那 1000 个 dead row versions,要等事务结束后才能清。

参数:autovacuum_freeze_max_age 默认从 2 亿抬到 100 亿

9 月 4 日查到的两套正式库检查点长得不一样。C014(54322)还是 epoch:xid 格式:

Latest checkpoint's NextXID:          0:1448

C016(54324)这里没有 0: 前缀,输出是一个纯数值。

参数用这条查:

SELECT name, setting, boot_val, source, vartype, max_val
FROM sys_settings
WHERE name IN (
  'vacuum_freeze_min_age',
  'vacuum_freeze_table_age',
  'autovacuum_freeze_max_age'
);
name C014 setting / 类型 / 最大 C016 setting / 类型 / 最大
autovacuum_freeze_max_age 200000000 / integer / 2000000000 10000000000 / int64 / 1152921504606846975
vacuum_freeze_min_age 50000000 / integer / 1000000000 50000000 / int64 / 1152921504606846975
vacuum_freeze_table_age 150000000 / integer / 2000000000 150000000 / int64 / 1152921504606846975

说明书 2.4.1 列出的这三项参数类型和上限与实测一致。autovacuum_freeze_max_age 的默认值从 2 亿提到 100 亿,这个数已经超过 C014 里该参数允许配置的上限 20 亿。三项参数的 setting 与 boot_val 一致,source 均为 default。

C014 往上限外面写:

SET vacuum_freeze_min_age = 1000000001;
ERROR:  1000000001 is outside the valid range for parameter "vacuum_freeze_min_age" (0 .. 1000000000)

SET vacuum_freeze_table_age = 2000000001;
ERROR:  2000000001 is outside the valid range for parameter "vacuum_freeze_table_age" (0 .. 2000000000)

同样两个值放到 C016,SET 都能进去,SHOW 分别是 1000000001、2000000001。autovacuum_freeze_max_age 两边都不能热改,要重启,C016 默认已经是 10000000000。

txid_current() 两边都返回 bigint,单看返回类型分不出内部宽度。我还对了 NextXID 的 epoch 前缀、freeze 参数的 vartype / max_val,以及 age(datfrozenxid) 的类型。最后这一项,C014 是 integer,C016 是 bigint。

两边对照

C014 C016
正式库 NextXID 格式(9 月 4 日) 0:1448,带 epoch 前缀 纯数值,无 epoch 前缀
首次跳号后 txid_current 2146470000 2146470000
回卷预警(9 月 4 日) 有,日志提示余量 1014764 未出现
旧快照保持期间的清理 1000 个 dead row versions 暂不能移除 相同
结束旧事务后再次清理 成功清除 1000 行 相同
第二次跳号后 kbbench 16000 笔,再 INSERT 成功(txid 2147494002) 成功(txid 2147494002)
SET vacuum_freeze_min_age=1000000001 超出范围 成功
autovacuum_freeze_max_age 默认 200000000 10000000000

C016 实测到的变化

C014 的 XID 年龄有 32 位编号空间的上限。余量耗尽时,日志 HINT 提示 database shutdown。实测里两边 NextXID 都是 2146470000,C014 报 must be vacuumed within 1014764 transactions,C016 没有这条。

freeze 三个参数的可调范围也变了。autovacuum_freeze_max_age 默认从 2 亿提到 100 亿,类型从 integer 变成 int64,已经超过 C014 的参数上限。vacuum_freeze_min_age 在 C014 上限是 10 亿,我写 1000000001 直接报错,同样的值在 C016 能设进去。C016 的防回卷冻结年龄上限比 C014 大得多。

NextXID 的格式和 age(datfrozenxid) 的类型也变了。C014 的检查点用 epoch:xid 记录 32 位 XID 的轮转,C016 的输出没有 epoch 前缀;age(datfrozenxid) 从 integer 变成 bigint。

长事务仍然会阻塞死元组清理。1000 个 dead row versions,C014 和 C016 都要等会话 A 结束事务才能清掉。C014 的长事务把冻结水位钉住后,系统继续分配 XID 会压缩可用余量,日志随后开始预警。C016 在这次相同 NextXID 测试中没有出现这条预警。

巡检和升级要动的地方

巡检脚本迁到 C016,age(datfrozenxid) 用 bigint 接,阈值别再写死 2 亿或 20 亿。NextXID 的解析也要改,C016 没有 epoch 前缀,还按冒号拆会取到错值。

说明书 2.1.4 针对列出的 Oracle 兼容版/模式,给出 sys_upgrade 和 dump/restore 两条升级路径。Catalog 号从 202602101 变成 202608061,不能只换 Server/bin 就去开 C014 的数据目录。这次 C016 是新 initdb,原地升级我没测。

长事务还是要查。8 月 31 日在兼容库上造 idle in transaction 会话时,我试过取消查询,事务没结束,终止连接后才回滚。

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论