背景
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;
然后就是慢慢的等待。。。




