PostgreSQL Greenplum crash 后临时表引发的BUG - 暨年龄监控的重要性
背景
PostgreSQL 和 Greenplum 都支持临时表。
在使用临时表时,如果数据库crash,临时表不会被自动清除,这样可能会埋下隐患,隐患爆发时是非常危险的。
问题在哪呢?
因为vacuum freeze不处理其他会话创建的临时表,仅仅处理当前会话创建的临时表。
也就是说,没有被清理的临时表,可能导致数据库年龄无法下降。
但是PostgreSQL从8.4的版本开始autovacuum进程就有了自动清理未正常删除的TEMP表的功能。
并且PostgreSQL从8.4的版本开始如果将来还会继续在同一个temp schema中创建临时表的话,同样也会自动清理以前的未清除的临时表。
代码:
http://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=5b965bf08bfb4aa8928bafaed20e42b89de02a5c
/*
* Check if it is a temp table (presumably, of some other backend's).
* We cannot safely process other backends' temp tables.
*/
if (classForm->relpersistence == RELPERSISTENCE_TEMP)
{
int backendID;
backendID = GetTempNamespaceBackendId(classForm->relnamespace);
/* We just ignore it if the owning backend is still active */
if (backendID == MyBackendId || BackendIdGetProc(backendID) == NULL)
{
/*
* We found an orphan temp table (which was probably left
* behind by a crashed backend). If it's so old as to need
* vacuum for wraparound, forcibly drop it. Otherwise just
* log a complaint.
*/
if (wraparound)
{
ObjectAddress object;
ereport(LOG,
(errmsg("autovacuum: dropping orphan temp table \"%s\".\"%s\" in database \"%s\"",
get_namespace_name(classForm->relnamespace),
NameStr(classForm->relname),
get_database_name(MyDatabaseId))));
object.classId = RelationRelationId;
object.objectId = relid;
object.objectSubId = 0;
performDeletion(&object, DROP_CASCADE, PERFORM_DELETION_INTERNAL);
}
else
{
ereport(LOG,
(errmsg("autovacuum: found orphan temp table \"%s\".\"%s\" in database \"%s\"",
get_namespace_name(classForm->relnamespace),
NameStr(classForm->relname),
get_database_name(MyDatabaseId))));
}
}
}
而greenplum现在还使用的是8.3的版本,这个BUG还存在:
例子:
会话A:
postgres=# create temp table tmp1(id int);
CREATE TABLE
不要断开会话
模拟数据库crash(-m immediate或kill -9某非数据库postmaster进程)
pg_ctl stop -m immediate
waiting for server to shut down.... done
server stopped
重启数据库
pg_ctl start
重启后,临时表并没有自动被清除。
postgres=# select nspname,relname,age(relfrozenxid) from pg_class a, pg_namespace b where a.relnamespace=b.oid and a.relname='tmp1';
nspname | relname | age
-----------+---------+-----
pg_temp_2 | tmp1 | 1
(1 row)
模拟几个事务:
postgres=# select txid_current();
txid_current
--------------
1798
(1 row)
postgres=# select txid_current();
txid_current
--------------
1799
(1 row)
postgres=# select txid_current();
txid_current
--------------
1800
(1 row)
postgres=# select txid_current();
txid_current
--------------
1801
(1 row)
这个临时表会导致数据库的年龄无法降低
postgres=# vacuum freeze;
VACUUM
postgres=# select nspname,relname,age(relfrozenxid) from pg_class a, pg_namespace b where a.relnamespace=b.oid and a.relname='tmp1';
nspname | relname | age
-----------+---------+-----
pg_temp_2 | tmp1 | 6
(1 row)
postgres=# select datname,age(datfrozenxid) from pg_database where datname='postgres';
datname | age
----------+-----
postgres | 6
(1 row)
如果不处理的话,久而久之,数据库可能会到达最大年龄,需要停库维护。
在修复这个BUG前的处理方法
drop table pg_temp_2.tmp1;
对于PostgreSQL 8.4以及以上的版本, 不需要太担心以上问题。
对于现在的Greenplum,可能存在的问题更复杂,比如只是某些SEGMENT上没有清理,某些清理了,则只能使用UTIL模式连接到GP的每个节点(包括master)取DROP TABLE。
但是注意不要删除当前其他会话正在使用的TEMP TABLE.
PGOPTIONS="-c gp_session_role=utility" psql -h xx -p xx -U xx -d xx
postgres=# select b.nspname,relname,age(relfrozenxid) from pg_class a ,pg_namespace b where a.relnamespace=b.oid and b.nspname ~ 'temp' and relkind='r';
nspname | relname | age
------------+---------+-----
pg_temp_10 | t | 388
pg_temp_7 | t | 350
pg_temp_9 | t | 508
(3 rows)
drop table xxx.xx;
这个隐患比长事务,2PC事务更隐蔽 , 排查时需要注意。