PostgreSQL Greenplum crash 后临时表引发的BUG - 暨年龄监控的重要性

1 minute read

背景

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事务更隐蔽 , 排查时需要注意。

Flag Counter

digoal’s 大量PostgreSQL文章入口