背景
PostgreSQL中xid(事务id)的数量是有限制的(32bit,约42.9亿个),其中有3个保留xid(0:无效的xid;1:初始化时创建catalog的xid;2:冻结xid),为了防止xid数量不足引起xid回卷的情况,使用冻结xid(FrozenTransactionId)来保证所有历史事务都比普通事务都老,数据被普通事务可见,且有足够的空闲xid供将来的事务使用。
需要定期执行vacuum操作或设置数据库参数来触发autovacuum进程将符合条件的事务xmin值更新为2(不同版本实现方式不一样)。即使关闭autovacuum参数且从未执行过vacuum命令,当事务的age达到了autovacuum_freeze_max_age参数值时,数据库依然会强制执行vacuum freeze命令。如果强制执行vacuum 失败,则有xid用尽的风险,当数据库的最旧 XID 和回卷点之间剩余1千万个事务时,系统将开始发出这样的警告消息
WARNING: database "mydb" must be vacuumed within 177009986 transactions HINT: To avoid a database shutdown, execute a database-wide VACUUM in "mydb".
复制
距离回卷点只剩下1百万个事务时,该系统将会关闭并且拒绝开始任何新的事务
ERROR: database is not accepting commands to avoid wraparound data loss in database "mydb" HINT: Stop the postmaster and vacuum that database in single-user mode.
复制
这时只能关闭数据库,然后以单用户模式进入相应的数据库执行vacuum操作
/bin/postgres --single -D /data/pgdata mydb backend> vacuum;
复制
经历
维护PostgreSQL数据库这么久,曾经遇到过一次这样的报警提示,原因是索引文件磁盘坏块,导致数据库无法有效的进行vacuum操作,并且这个索引也无法执行reindex命令进行重建,只好把这个索引删除重建创建,数据库可以正常进行autovacuum操作,问题得到解决。
重现原因
华为公司开源了opengauss数据库,而这个数据库是基于PostgreSQL 9.2.4的内核版本进行封装开发的,opengauss数据库改进的一个特性是将postgresql的xid限制由32位提升到了64位,这也就意味着opengauss的xid永远用不完,也就不会涉及到事务回卷的情况发生,为了验证这一点,需要PostgreSQL 和 opengauss 一起来验证。
重现步骤
问题重现有一个难点就是当age达到autovacuum_freeze_max_age参数值后如何有效的阻止数据库强制执行vacuum freeze操作。
事务id不能像序列那样指定初始值,只能以interval=1的方式自增,所以没有捷径可以走,只能刷事务id。
数据库安装
这里使用的是PostgreSQL 9.2.4 源码安装,安装步骤略
参数修改
autovacuum_freeze_max_age = 2000000000 # maximum XID age before forced vacuum vacuum_freeze_min_age = 1000000000 vacuum_freeze_table_age = 2000000000
复制
重现
有效阻止数据库出发vacuum freeze的方式有两种:
1、新增一个表,通过begin命令开启一个事务,执行一个ddl 或 dml 操作,让表或表中的一行数据持有排他锁,并且事务不提交不回滚,无法让vacuum获取锁,这时新开一个会话开始刷事务id
2、新增一个表,插入几条数据,然后直接在系统里删除数据文件(和之前遇到的索引问题类似),找不到数据文件,无法执行vacuum操作,这时新开一个会话开始刷事务id
由于我自己机器的配置太低,用第一种方法不能准确的控制会话连接,所以我选择用第二种方式
建表
新建两张表,各插入一条数据,查看当前数据的事务id,当前数据所在表的冻结id以及当前表所在数据库的冻结id
create table vacuum_test(id int); create table vacuum_test_1(id int); insert into vacuum_test select 1; insert into vacuum_test_1 select 1; select xmin,age(xmin),* from vacuum_test; select relname,relfilenode,relfrozenxid from pg_class where relname like 'vacuum_test%'; select oid,datname,datfrozenxid from pg_database where datname='mydb';
复制
重命名数据文件
通过上面的命令知道了表所对应的物理文件名(relfilenode),在base/数据库oid/relfilenode
直接用mv 命令替换就好,比如
mydb=# select xmin,age(xmin),* from vacuum_test; xmin | age | id -----------+-----+---- 636973319 | 1 | 1 (1 row) mydb=# select relname,relfilenode,relfrozenxid from pg_class where relname like 'vacuum_test%'; relname | relfilenode | relfrozenxid -------------+-------------+-------------- vacuum_test | 24605 | 636973318 (1 row) mydb=# select oid,datname,datfrozenxid from pg_database where datname='mydb'; oid | datname | datfrozenxid -------+---------+-------------- 24604 | mydb | 636973312 (1 row) $ ll base/24604/24605 -rw------- 1 postgres postgres 8192 Sep 6 00:19 base/24604/24605 $ mv base/24604/24605 base/24604/24605_old $ ll base/24604/24605* -rw------- 1 postgres postgres 8192 Sep 6 00:24 base/24604/24605_old
复制
pgbench 批量执行
人工刷txid太慢了,用压测工具来跑,省时省力
pgbench 工具安装很简单
在postgresql的源码文件contrib/pgbench下执行 make && make install
cat pgbench.sql BEGIN; select txid_current(); end; --执行 pgbench -c 60 -j 60 -n -f pgbench.sql -T 36000 mydb & 开启的客户端数量和线程数量根据服务器硬件配置改动
复制
对比测试
定时执行一次下面的命令,跟踪一下
insert into vacuum_test_1 select 1; select xmin,age(xmin),* from vacuum_test_1; select relname,relfilenode,relfrozenxid from pg_class where relname like 'vacuum_test%'; select oid,datname,datfrozenxid from pg_database;
复制
然后就是慢慢的等待。。。
评论
