[Greenplum]greenplum中reindex pg_class后发生狂读pg_class的问题分析

 

  最近在维护greenplum中,发现pg_class表的索引很大了,为了增加提高性能,就做了一个reindex table pg_class;但做完后,就发现整个greenplum数据库的性能降低到只有以前的几分之一了,通dtrace脚本跟踪发现是一直在狂读pg_class,也不清楚为什么。当时没有办法,只好新建了一张与pg_class结构和索引都完全一样的表,然后把pg_class的内容导到这个新建的表中,然后关闭数据库,交换两个表的底层文件后,故障消失了。当时是一直不明白为什么,也把这个问题报给了EMC公司,也一直没有定位到原因。由于我们还有一个容灾环境,在容灾环境上建了一个greenplum的clone,然后在这个环境上也重现的故障。通过几天的定位,终于把原因找到了。

     原因是当重建pg_class表的索引时,导致索引pg_class_oid_index在pg_class表中的物理位置发生了变化。

原先物理位置在0号块,

aligputf8=# select ctid,relname,oid from pg_class where oid=2662;

  ctid   |      relname       | oid  

---------+--------------------+------

 (0,108) | pg_class_oid_index | 2662

 

reindex table pg_class后,索引pg_class_oid_index的位置移到了文件的最后:

aligputf8=# select ctid,relname,oid from pg_class where relname='pg_class_oid_index';

    ctid    |      relname       | oid  

------------+--------------------+------

 (43585,32) | pg_class_oid_index | 2662

 

而为什么索引pg_class_oid_index在pg_class表中的条 目的物理位置移到最后就会导致狂读pg_class的问题发生呢?

原因是,当访问任何一个表时,就需要从pg_class表中提取这个表的元数据信息,而在pg_class中查找表的原数据信息,需要访问索 引pg_class_oid_index,而访问索引pg_class_oid_index,也需要从pg_class表中获得索引pg_class_oid_index的元数据信息,而获得索引pg_class_oid_index自己的元数据信息,就不能再通过索引自己去查找自己的信息了,这里就只能从头扫描表pg_class来获得,而由于索引pg_class_oid_index的条目被移到了最后的位置,所以导致需要几乎把pg_class从头 扫描到尾才能找到pg_class_oid_index的条目,这样就大大的降低了数据库的性能。

以前也做过pg_class,为什么没有出来这个问题呢?我和任振中也做了测试发现,当原先的0号块中有空闲空间时,做reindex时,索引pg_class_oid_index的 条目仍会在0号块中,这样就不会出现上面的问题了。

由此可知,在greenplum中是不能随便对pg_class表做reindex了。而这个问题在PostgreSQL数据库中是否存在问题,目前也没有测试过。

 

下面是定位问题的过程:

 

分析过程:

当发生狂读时,使用pstack <pid>可以看到程序的堆栈情况:

13807:  /opt/greenplum/greenplum-db-4.1.1.8/bin/postgres -D /storagepool/gptem

-----------------  lwp# 1 / thread# 1  --------------------

 fffffd7ffec326ea _read () + a

 00000000009311ff FileRead () + cf

 000000000097210e mdread () + 12e

 0000000000974a7b smgrread () + 1b

 000000000092b24d ReadBuffer_Ex () + bad

 0000000000559726 heapgetpage () + 5f6

 0000000000559d63 heapgettup () + 493

 000000000055d6f2 heap_getnext () + d2

 0000000000abab53 RelationReloadClassinfo () + 93

 0000000000abe728 RelationIdGetRelation () + 88

 000000000055bc8f relation_open () + 1f

 000000000056d135 index_open () + 15

 000000000056c110 systable_beginscan () + b0

 0000000000abd6d9 RelationBuildDesc () + 89

 0000000000abe745 RelationIdGetRelation () + a5

 000000000055e2b6 heap_open () + 16

 00000000007b1f69 ExecInitSeqScan () + b9

 000000000077d01a ExecInitNode () + 18a

 0000000000798393 ExecInitAgg () + 1e3

 000000000077d12a ExecInitNode () + 29a

 00000000007aebb5 ExecInitMotion () + 1b5

 000000000077d17a ExecInitNode () + 2ea

 0000000000799438 ExecInitAgg () + 1288

 000000000077d12a ExecInitNode () + 29a

 00000000007b7dbc ExecInitSubqueryScan () + 1dc

 000000000077d0aa ExecInitNode () + 21a

 0000000000795ec9 ExecInitAppend () + 4b9

 000000000077cfea ExecInitNode () + 15a

 00000000007b4f19 ExecInitSort () + 1b9

 000000000077d11a ExecInitNode () + 28a

 00000000007b8f35 ExecInitUnique () + 145

 000000000077d13a ExecInitNode () + 2aa

 00000000007b7dbc ExecInitSubqueryScan () + 1dc

 000000000077d0aa ExecInitNode () + 21a

 0000000000799438 ExecInitAgg () + 1288

 000000000077d12a ExecInitNode () + 29a

 0000000000775e95 InitPlan () + a45

 0000000000776d6b ExecutorStart () + 19b

 0000000000986cf4 PortalStart () + 504

 0000000000981702 PostgresMain () + 2312

 00000000008f0f4b ServerLoop () + 113b

 00000000008f2e20 PostmasterMain () + f60

 0000000000800dda main () + 48a

 00000000005126fc _start () + 6c

 

而没有发生pg_class时,使用dtrace看到了堆栈情况:

postgres`RelationIdGetRelation

postgres`heap_open+0x16

postgres`RelationReloadClassinfo+0x53

postgres`RelationIdGetRelation+0x88

postgres`relation_open+0x1f

postgres`index_open+0x15

postgres`systable_beginscan+0xb0

postgres`RelationBuildDesc+0x89

postgres`RelationIdGetRelation+0xa5

postgres`heap_open+0x16

postgres`ExecInitSeqScan+0xb9

postgres`ExecInitNode+0x18a

postgres`ExecInitAgg+0x1e3

postgres`ExecInitNode+0x29a

postgres`ExecInitMotion+0x1b5

postgres`ExecInitNode+0x2ea

postgres`ExecInitAgg+0x1288

postgres`ExecInitNode+0x29a

postgres`ExecInitSubqueryScan+0x1dc

postgres`ExecInitNode+0x21a

 

在正常情况下和异常情况下,都调用了index_open函数,那么这个函数打开了哪个索引呢,使用下面的简单的脚本,就可以知道此处打开的索引 的oid为2662:

 

[root@dw-rzgp08 /export/home/gpadmin/tang]#cat mytrace.sh

#!/bin/bash

 

/usr/sbin/dtrace -q -n "

pid$1:*:RelationIdGetRelation:entry

{

    self->oid=arg0;

    printf(\"entry: %s, oid=%d\n\", probefunc, arg0);

    ustack();

}

 

pid$1:*:RelationIdGetRelation:return

{

    printf(\"return : %s, oid=%d\n\", probefunc, self->oid);

}

"

 

对比两种情况:

异常情况下:

 fffffd7ffec326ea _read () + a

 00000000009311ff FileRead () + cf

 000000000097210e mdread () + 12e

 0000000000974a7b smgrread () + 1b

 000000000092b24d ReadBuffer_Ex () + bad

 0000000000559726 heapgetpage () + 5f6

 0000000000559d63 heapgettup () + 493

 000000000055d6f2 heap_getnext () + d2

 0000000000abab53 RelationReloadClassinfo () + 93

 

正常情况下:

postgres`RelationIdGetRelation

postgres`heap_open+0x16

postgres`RelationReloadClassinfo+0x53

 

从上面的堆栈可以知道,访问索引 pg_class_oid_index时,当在异常情况下时发生了狂读pg_class的情况。

由PostgreSQL数据库原理知道,当访问一个表的时候,需要先获得这个表的元数据信息,而获得表的元数据信息是需要查询pg_class表 的,

这个查询是通过索引pg_class_oid_index来快速访问的,而如果索引pg_class_oid_index本身的元数据不在共享内 存中或syscache中时,需要通过全表扫描pg_class来找到索引pg_class_oid_index的元数据信息,因为查询索引 pg_class_oid_index的元信息,不能通过索引自己去查找,只能全表扫描。

 

没有发生狂读的结点的pg_class_oid_index在pg_class中的位置为(0,108),

aligputf8=# select ctid,relname,oid from pg_class where oid=2662;

  ctid   |      relname       | oid 

---------+--------------------+------

 (0,108) | pg_class_oid_index | 2662

 

而有问题的结点索引pg_class_oid_index在pg_class中的位置为(43585,32):

aligputf8=# select ctid,relname,oid from pg_class where relname='pg_class_oid_index';

    ctid    |      relname       | oid 

------------+--------------------+------

 (43585,32) | pg_class_oid_index | 2662

 

 

对没有问题的结点做reindex table pg_class,多做几次,发现索引pg_class_oid_index在pg_class表中的位置也会被移到最后。

由此可见知道是这个原因导致了狂读pg_class的问题。

 

而为什么直接连接segment节点,没有这个问题了,测试时发现,如果数据库启动后,第一次login进数据库时,也会发生一次狂读 pg_class的情况,但这之后,由于pg_class的数据被cache在的共享内存,后面的新连接,就可以在内存中找到了,就不会再调用 RelationReloadClassinfo,故不发生这个问题了。而从master发过来的连接,不知道为什么总是要调用 RelationReloadClassinfo,从而总是发生狂读的情况。

由于PostgreSQL8.2的源代码中RelationReloadClassinfo()函数中并没有直接调用 heap_getnext(),所以估计是greenplum的源代码是与PostgreSQL是不一样的,在PostgreSQL中的原理中对 函数RelationReloadClassinfo的描述为:

/*

 * RelationReloadClassinfo - reload the pg_class row (only)

 *

 *    This function is used only for indexes.  We currently allow only the

 *    pg_class row of an existing index to change (to support changes of

 *    owner, tablespace, or relfilenode), not its pg_index row or other

 *    subsidiary index schema information.  Therefore it's sufficient to do

 *    this when we get an SI invalidation.  Furthermore, there are cases

 *    where it's necessary not to throw away the index information, especially

 *    for "nailed" indexes which we are unable to rebuild on-the-fly.

 *

 *    We can't necessarily reread the pg_class row right away; we might be

 *    in a failed transaction when we receive the SI notification.  If so,

 *    RelationClearRelation just marks the entry as invalid by setting

 *    rd_isvalid to false.  This routine is called to fix the entry when it

 *    is next needed.

 */

而在greenplum中是如何触发这个函数的,就不清楚了。