RAC Object Remastering (Dynamic Remastering)

原文作者:Riyaj Shamsudeen
原文链接:http://orainternals.wordpress.com/2010/03/25/rac-object-remastering-dynamic-remastering/

译者注:如果您对本文章有兴趣,请一定先去阅读Riyaj的原文,在万不得已时可以使用这篇译文校对一下自己的理解。译文中的master, mastering, remastering, affinity lock都翻译的不满意,甚至有些词左思右想还是保留了原文。IT技术从业者应该以英文为母语。

感谢Yong Huang,从跟他的邮件往来中得到了很多帮助。

***********以下为译文全文***********。
RAC Object Remastering (Dynamic Remastering)
by Riyaj Shamsudeen

在RAC环境中,每个数据块都被一个实例所掌控(mastered)。掌控一个数据块意思是主实例(master instance)始终监控这个数据块的状态,直到下一次重配置事件发生(因为实例重启或者其它原因)。

Hash to the master
这些数据库是根据数据块范围来掌控的。举例来说,数据块范围从file 10,block 1开始到block 128可能是被实例1所掌控,而从file 10,block 129到block 256是被实例2所掌控。当然,在不同的版本10g,11g中都有所不同,只是中心思想都是将数据块均衡地分布在不同实例上由此Global Cache grants可以在各实例间平衡分布。有趣的是,从10g以后版本数据块范围的长度是128(Julian Dyke提到在9i中是1089,但是我个人还没有测试过)。技术支持推荐取消db_file_multiblock_read_count初始化参数的设置这样将会自动调整到128,我猜这意味着通过较少的GC消息就可以进行完整的多块读。走题了。

更深入的,Michael Möller指出这种哈希算法有进一步优化:当通过数据块地址(DBA)初次计算主节点时使用到哈希算法,生成一个“虚拟属主”,然后在通过一个查询表(长度是最大允许的节点数,128?)再转化为真实(在线且打开的)属主。这意味着当有一个节点关闭或者启动的时候,RAC不需要为所有数据块再次计算哈希值,而只需要分发新的Hash-to-node表。(这张表在节点发生变化时也同样需要重新分发)

以下SQL有助于显示数据块的属主(master)和拥有者(owner)。这个SQL结合了x$kjbl和x$le表来获得资源名称。如果你对于Oracle锁机制比较熟悉,你可能认识这些缓存融合(cache fusion)锁(以前的PCM锁)的结构。在这里锁类型是BL,id1是block#,id2是file_id。列kjblname2显示的是锁资源的十进制数字格式。

请注意下面的输出:
1. 数据块范围:File 1, block 6360-6376为node 3所掌控同时也属于node 3
2. 数据块范围:File 1,一直到10709数据块为实例0所掌控,属于实例3
3. 从10770开始的下一个数据块范围为实例2所掌控并且属于实例3

还请注意这些输出是从一个还没有remastering操作的数据库中获得的。

REM kjblname2列逗号前第一部分是数据块,第二部分file_id*65536是BL locks
[sourcecode language=”sql”]select kj.*, le.le_Addr from (
select kjblname, kjblname2, kjblowner, kjblmaster, kjbllockp,
substr ( kjblname2, instr(kjblname2,’,’)+1, instr(kjblname2,’,’,1,2)-instr(kjblname2,’,’,1,1)-1)/65536 fl,
substr ( kjblname2, 1, instr(kjblname2,’,’)-1) blk
from x$kjbl
) kj, x$le le
where le.le_kjbl = kj.kjbllockp
order by le.le_addr
/

KJBLNAME KJBLNAME2 KJBLOWNER KJBLMASTER FL BLK LE_ADDR
———————- ————— ———- ———– — —– —————-
[0x18d8][0x10000],[BL] 6360,65536,BL 3 3 1 6360 000000038DF9AB08

[0x18e7][0x10000],[BL] 6375,65536,BL 3 3 1 6375 000000038DFBF818 => case #2
[0x18e8][0x10000],[BL] 6376,65536,BL 3 3 1 6376 000000038DFD3BA0

[0x29d1][0x10000],[BL] 10705,65536,BL 3 0 1 10705 00000005D6FE9230

[0x29d5][0x10000],[BL] 10709,65536,BL 3 0 1 10709 000000038EFBB990
[0x2a12][0x10000],[BL] 10770,65536,BL 3 2 1 10770 000000075FFE3C18

[0x2a18][0x10000],[BL] 10776,65536,BL 2 2 1 10776 000000038EFA8488 => case #1
[0x2a19][0x10000],[BL] 10777,65536,BL 3 2 1 10777 000000038EFB7F90
[0x2a1a][0x10000],[BL] 10778,65536,BL 1 2 1 10778 000000038EFCC318
[/sourcecode]

让我们看一下在实例3上运行3种简单SELECT语句的情况:
1. 一个会话尝试读取数据块file 1,block 10776,但是这个数据块被实例2掌控同时又被实例2拥有(也就是该块现在存在于实例2的buffer cache中)。所以,实例3将给实例2发送一个PR(Protected Read)模式的BL锁要求读取该块。更加复杂的情况我们先忽略掉,实例2将授予实例3一个PR模式锁并且将数据块传送给实例3。显然,这将会有多个GC消息、锁授予和块传递。统计值‘gc remote grants’也会增加。
2. 假设此会话尝试读取数据块file 1, block 6375。此块为实例3掌控同时被实例3拥有,那这时候就不会有额外的GCS/GES过程了,会话直接pin住buffer然后继续工作。
3. 考虑第三种情况。会话尝试读取数据块file 1 block 6374。此块不在任何实例的buffer cache中,但是实例3是这个块的属主,所以只需要消耗少量GC消息和等待而获得本节点的affinity locks,然后将数据块从磁盘读到缓存中来。
在上面的情况2和情况3中,请求者实例同时也是数据块或者块范围的属主节点。在这种情况下,统计值‘gc local grants’增加,这些块上获得的本节点的affinity locks代价较低,这样避免了大量的全局缓存消息。

目前为止一切都很好,但是如果有这样一种情况,实例1在频繁读取一个对象(表、索引等),而其他实例则完全不读取该对象呢(某些应用的代码分区策略或者负载策略就会产生这种现象)?频繁读取一个对象的实例需要为每次OPEN该对象数据块的操作向远程实例请求授权,这合理吗?特别是,当数据块被读进buffer cache,但是又很快从buffer cache中消失的时候呢?如果说频繁读取某个对象的实例又恰恰是这个对象的属主,就像我们在情况2和情况3中看到的那样,这岂不是更好吗?
除此之外,如果数据块即将从缓冲池中移走或者即将被覆盖,就会导致在主实例和其它实例之间的额外消息开销,因为所有权信息需要再被送回块的属主处。

进入对象remastering主题。

Object Remastering
在10g/11g RAC中有很多新功能。其中一个就是对象remastering。这个特性在10gR1中引入,10gR2中得到提高,11g中的得到进一步增强。我注意到其实在9i中也有相应的参数,不过我认为实际上并不能正常工作。
有了对象remastering特性,如果一个对象被某个实例频繁读取,那么这个实例将会成为这个对象的master以此来减少gc remote grants从而提高整个应用的性能。在前面这句话中我用到了“读取(accessed)”这个词,但是实际上并不太贴切,更准确的说法是如果某个实例请求了一个对象上的很多BL锁,那么这个对象将remastered。在完美世界中,即使应用程序没有完善的分区策略,remastering被某个实例频繁读取的对象这样的举措也会因为获取的是更廉价的本地实例亲和锁(affinity locks),从而使RAC开销降到最小。

好吧,我也说了是在完美世界中。实际上这里有几个问题:
1. 重启以后实例不会记住之前的属主情况。这意味着每次重新启动实例都需要重新学习对象的属主情况图。我能预计想要记住属主情况是比较复杂的,但是我认为还是有可能实现的。
2. Remastering的代价并不低。在重配置时实例GRD会被冻结(frozen),而繁忙时这个操作可能需要几秒钟,这将会导致整个实例在这几秒内被冻结。虽然10gR2推出了并行重配置(parallel reconfiguration)(通过_rcfg_parallel_replay初始化参数控制)使用所有LMS进程来完成重配置,但是几秒钟的系统冻结在很多环境中还是完全不能被接受的。
3. 我建议我的客户将LMS进程保持在较低的个数(最多3-5),但是如果降低LMS进程数目,那么实例重配置的有效并行度也会降低。
4. 最后,但并不是最不重要的,对于较繁忙的环境来说,触发remastering事件的默认参数值通常过低,因此导致了频繁的对象remastering。在EBS环境中,对于manager的配置稍稍处理不善就可能导致大量的重配置问题。

Parameters, views and internals
控制remastering行为的有几个参数,并没有很好的文档说明。我的测试结果也并不是非常精确。但是,这些参数还是给了我们一个内部到底如何运作的大体概貌。这些在10gR2版本以前是有效的。而对于11g,整套参数都不一样了,我将另写一篇文章来描述这些改变。

x$object_affinity_statistics维护对象和对象上的OPENs的统计信息。理解OPEN和Buffer Access之间的区别是重要的。如果缓存中的数据块已经处于适当的模式,就没有必要在数据块上打开BL锁,所以如果会话反复存取同一个数据块而不请求额外的BL锁,那么计数就不会增加,因此所谓OPEN就是某一刻的时间帧内对于BL锁的请求计数。

LCK0进程维护着这些对象affinity的统计信息。如果一个实例在比另一实例在某一对象上多打开了50个open(由_gc_affinity_limit参数控制)(译者注:实际上在超过2节点的RAC中,该参数的意思是如果一个实例比环境中其它所有实例访问某一个对象的总和还要多50),那么该对象将成为remastering候选者。该对象被放入队列,LMD0进程读取队列,开始GRD冻结。LMON进程协同LMS进程一起执行buffer cache locks的重配置。所有的这些动作都可以在LMD0/LMON trace文件中找到。初始化参数_gc_affinity_time控制队列的检查间隔,以确认remastering是否要触发,默认值是10分钟。

现在,你不愿看到无论哪个对象都能成为候选者而发生remastering,也就是说,如果仅仅是实例1在某对象上打开了101个BL锁而实例2打开了50个BL锁,那么你不想触发该对象的remastering。只有那些有很高的BL锁请求的对象才应该被放入队列并被remastering。此阀值被另一个参数_gc_affinity_minimum控制,只有超过该参数定义的 “每分钟动态affinity活动的最小数量”的对象才会成为候选者而发生remastering。默认值是2500,我认为这在繁忙的系统中有些低。(译者注:在11g中,默认值是1500,更低)
LMD0 trace文件中的以下几行表示LMD0进程正在读取请求队列:

* kjdrchkdrm: found an RM request in the request queue
  Transfer pkey 6589904 to node 3
*** 2009-10-12 11:41:20.257

How bad can it get?
Remastering问题发生时会影响性能。以下AWR报告显示了DRM重配置问题导致的实例冻结。同样类型的冻结在其它的所有节点上也都可以看见。gc buffer busy等待只是DRM冻结的副作用(不是总有,但是在这个case是这样)。

Top 5 Timed Events                                         Avg %Total
~~~~~~~~~~~~~~~~~~                                        wait   Call
Event                                 Waits    Time (s)   (ms)   Time Wait Class
------------------------------ ------------ ----------- ------ ------ ----------
gc buffer busy                    1,826,073     152,415     83   62.0    Cluster
CPU time                                         30,192          12.3
enq: TX - index contention           34,332      15,535    453    6.3 Concurrenc
gcs drm freeze in enter server       22,789      11,279    495    4.6      Other
enq: TX - row lock contention        46,926       4,493     96    1.8 Applicatio

在同一时刻,DRM大量发生,导致了重复的DRM冻结、实例重配置,从而引发严重的性能问题。

* received DRM start msg from 2 (cnt 5, last 1, rmno 404)
Rcvd DRM(404) Transfer pkey 1598477 from 3 to 2 oscan 1.1
Rcvd DRM(404) Dissolve pkey 6100030 from 2 oscan 0.1
Rcvd DRM(404) Dissolve pkey 5679943 from 2 oscan 0.1
Rcvd DRM(404) Transfer pkey 6561036 from 0 to 2 oscan 1.1
Rcvd DRM(404) Transfer pkey 5095243 to 2 oscan 0.1
...

A small test case
我们来看一个测试,观察一下DRM的产生。使用了索引读路径的查询从一个大索引中读取了几乎所有数据块。
[sourcecode language=”sql”]Session #1:
select data_object_id from dba_objects where object_name=’WMS_N1′;
DATA_OBJECT_ID
————-
6099472

REM 还没有affinity统计信息
select * from x$object_affinity_statistics where object=6099472;
no rows selected.

REM 执行高代价的查询语句
select /*+ index(a WMS_N1) */ count(*) from big_table1 a;

Session #2: 观察DRM表:
REM DRM字段现在是409,。我们将持续观察这个数值。在这个视图中还有其它比较有趣的字段。
select drms from X$KJDRMAFNSTATS;
DRM
—-
409

REM 观察到自从会话#1开始运行在该索引上已经有23442个OPENs
select * from x$object_affinity_statistics where object=6099472;
ADDR INDX INST_ID OBJECT NODE OPENS
—————- ———- ———- ———- ———- ———-
FFFFFFFF7C05BFA8 14 1 6099472 1 23442

REM 该对象上还没有发生mastering
select * from v$gcspfmaster_info where object_id=6099472;
no rows selected

REM 几秒后,OPENs计数从23442上升到33344
select * from x$object_affinity_statistics where object=6099472;
ADDR INDX INST_ID OBJECT NODE OPENS
—————- ———- ———- ———- ———- ———-
FFFFFFFF7C05BFA8 16 1 6099472 1 33344

REM该对象上还没有发生mastering
select * from v$gcspfmaster_info where object_id=6099472;
no rows selected

REM 让人诧异,会话#1还在运行,OPENs计数忽然清零即使DRM还未发生过
REM OPENs从0又升到1229因为会话#1还在运行
ADDR INDX INST_ID OBJECT NODE OPENS
—————- ———- ———- ———- ———- ———-
FFFFFFFF7C05BFA8 0 1 6099472 1 1229

REM 大约10分钟以后,remastering发生了。DRMS从409上升到411。
select drms from X$KJDRMAFNSTATS;
DRM
—-
411

REM 该索引的remastering发生了。现在该对象的属主是0,也就是实例1
select * from v$gcspfmaster_info where object_id=6099472;
FILE_ID OBJECT_ID CURRENT_MASTER PREVIOUS_MASTER REMASTER_CNT
———- ———- ————– ————— ————
0 6099472 0 32767 1

REM OPENs还在上升,但是remastering已经发生了。
select * from x$object_affinity_statistics where object=6099472;
ADDR INDX INST_ID OBJECT NODE OPENS
—————- ———- ———- ———- ———- ———-
FFFFFFFF7C05AF78 2 1 6099472 1 42335
FFFFFFFF7C05AF78 3 1 6099472 2 1

REM LMON trace文件也指出pkey的传递。注意pkey和object_id是相同的。
*** 2010-03-23 10:41:57.355
Begin DRM(411) – transfer pkey 6099472 to 0 oscan 0.0
ftd received from node 1 (4/0.30.0)
all ftds received

REM 几秒后,OPENs被再次重置。
select * from x$object_affinity_statistics where object=6099472;
ADDR INDX INST_ID OBJECT NODE OPENS
—————- ———- ———- ———- ———- ———-
FFFFFFFF7C05BFA8 0 1 6099472 1 7437

REM 属主仍然是实例1。
select * from v$gcspfmaster_info where object_id=6099472;
FILE_ID OBJECT_ID CURRENT_MASTER PREVIOUS_MASTER REMASTER_CNT
———- ———- ————– ————— ————
0 6099472 0 32767 1[/sourcecode]

我们的测试表明,该索引上发生了大量的BL锁请求,之后对象被remastering。

Undo and affinity
回滚段的Mastering和其它段不同。对于非回滚段而言,一个段上的所有数据块通过哈希算法被分散在各个实例间。只有在经过大量的BL锁请求以后,段才会被mastering。但是对于回滚段而言,激活了一个回滚段的实例立刻成为该段的属主。这是合乎情理的,因为在大多数情况下回滚段将会被打开这个segment的实例使用。初始化参数_gc_undo_affinity控制这种动态undo remastering动作是否发生。

因为回滚段没有真正的object_id,所以使用4294950912作为虚拟object_ids的基准值。比如说,回滚段1(usn=1)的object_id是4294950913,回滚段2(usn=2)的object_id就是4294950914,依次类推(4294950912 = power(2,32) – power (2,14) = xFFFFC000)。

[sourcecode language=”sql”]select objecT_id, object_id-4294950912 usn, current_master, previous_master,
remaster_cnt from v$gcspfmaster_info where object_id>4294950912;

OBJECT_ID USN CURRENT_MASTER PREVIOUS_MASTER REMASTER_CNT
———- ———- ————– ————— ————
4294950913 1 0 32767 1
4294950914 2 0 32767 1
4294950915 3 0 32767 1
4294950916 4 0 32767 1
4294950917 5 0 32767 1
4294950918 6 0 32767 1
4294950919 7 0 32767 1
4294950920 8 0 32767 1
4294950921 9 0 32767 1
4294950922 10 0 32767 1
4294950923 11 1 32767 1
4294950924 12 1 32767 1
4294950925 13 1 32767 1

REM 注意usn 0在两个实例中都存在,这是系统回滚段。
REM 下面结果显示,头10个回滚段被node1掌控,而接下来的3个被实例2掌控。
select inst_id, usn, gets from gv$rollstat where usn <=13 order by inst_id, usn;
INST_ID USN GETS
———- ———- ———-
1 0 3130
1 1 108407
1 2 42640
1 3 43985
1 4 41743
1 5 165166
1 6 43485
1 7 109044
1 8 23982
1 9 39279
1 10 48552
2 0 4433
2 11 231147
2 12 99785
2 13 1883[/sourcecode]

我没有成功试验出触发下一次回滚段remastering。我创建了一个活动事务在一个节点上产生了200K回滚数据块,然后另外一个节点在读这个表,我观察到在回滚数据块上有大量等待。但是我没有发现这个回滚段上有任何remastering事件。无法确认为什么没有产生,也许是回滚段remastering的条件有所不同吧。

译者注:回滚段的remastering是不会因为另外一个节点对于回滚段有大量读取而发生的,只有在某个实例失效,然后负责进行实例恢复的另外那个实例会暂时的成为这些回滚段的master,这是为了进行实例恢复的需要,在恢复完毕以后所有的undo buffers都会被flush到磁盘上。

PS:我能够使用后面将描述的lkdebug命令手动remaster回滚段,所以oracle也应该可以自动remaster回滚段,只是可能还有一个其它条件也必须满足。

[sourcecode language=”sql”]select * from v$gcspfmaster_info where object_id=431+4294950912;
FILE_ID OBJECT_ID CURRENT_MASTER PREVIOUS_MASTER REMASTER_CNT
———- ———- ————– ————— ————
0 4294951343 0 32767 1

Oradebug lkdebug –m pkey 4294951343[/sourcecode]

* kjdrchkdrm: found an RM request in the request queue
  Transfer pkey 4294951343 to node 1
*** 2010-03-24 12:47:29.011
Begin DRM(520) - transfer pkey 4294951343 to 1 oscan 0.1
 ftd received from node 0 (4/0.31.0)
 all ftds received

[sourcecode language=”sql”]select * from v$gcspfmaster_info where object_id=431+4294950912;
SQL> /
FILE_ID OBJECT_ID CURRENT_MASTER PREVIOUS_MASTER REMASTER_CNT
———- ———- ————– ————— ————
0 4294951343 1 0 2[/sourcecode]

我不是在劝你们应该去修改这些隐含参数。只是要去理解这些参数,如果你们碰到诸如’gc remaster’, ‘gcs freeze for instance reconfiguration’这样的等待事件,那么应该知道是不是因为默认值太低了。跟技术支持沟通尝试是否能够调整。

Manual remastering
你可以使用oradebug命令来手动remaster一个对象:
[sourcecode language=”sql” light=”true”]oradebug lkdebug -m pkey[/sourcecode]

这会将一个对象remaster请求放入队列。LMD0和LMON进程会完成这个请求。

*** 2010-01-08 23:25:54.948
* received DRM start msg from 1 (cnt 1, last 1, rmno 191)
Rcvd DRM(191) Transfer pkey 6984154 from 0 to 1 oscan 0.0
 ftd received from node 1 (8/0.30.0)
 ftd received from node 0 (8/0.30.0)
 ftd received from node 3 (8/0.30.0)

当前属主是从0开始计数的。

[sourcecode language=”sql”]select * from v$gcspfmaster_info where object_id=6984154;
SQL> /
FILE_ID OBJECT_ID CURRENT_MASTER PREVIOUS_MASTER REMASTER_CNT
———- ———- ————– ————— ————
0 6984154 1 0 2

SQL> oradebug lkdebug -m pkey 6984154
Statement processed.

SQL> select * from v$gcspfmaster_info where object_id=6984154;

FILE_ID OBJECT_ID CURRENT_MASTER PREVIOUS_MASTER REMASTER_CNT
———- ———- ————– ————— ————
0 6984154 2 1 3[/sourcecode]

Summary
总结一下,remarstering是个很棒的功能。不过遗憾的是,我们有时候会成为它负面效果的受害者。所以,如果你碰到remastering引起的问题,不要直接就禁用它,而是应该去看看能否调优那些参数从而控制remastering事件。如果你仍然想完全禁用DRM,那么我建议设置_gc_affinity_limit和_gc_affinity_minimum参数到一个较高值,比如说1千万。将参数_gc_affinity_time设置为0也可以完全禁用DRM,但是这也意味着再也无法手工remaster对象了。另外,Arup也提到如果DRM被禁用那么x$object_affinity_statistics表也不会再被维护。

再次提醒,这些是隐含参数。在你修改这些参数之前确认Oracle Support同意你的举动。

Update 1:
从11g开始,affinity管理更名为policy管理(策略管理)。比如说,x$object_affinity_statistics表改名为x$object_policy_statistics,与之相似的,初始化参数也发生了改变:参数_gc_affinity_limit改名为_gc_policy_limit;参数_gc_affinity_time改名为_gc_policy_time;出现了一个新的视图v$policy_history,其中所有policy_event = ‘initiate_affinity’的记录都是与DRM事件相关的。
本blog的其它部分仍然没问题,除了默认的_gc_policy_limit参数值降低为1500,这意味着,在理论上,11g可能会产生更多的DRM事件。
[sourcecode language=”sql”] 1* select * from v$policy_history
INST_ID POLICY_EVENT DATA_OBJECT_ID TARGET_INSTANCE_NUMBER EVENT_DATE
———- ——————– ————– ———————- ——————–
2 glru_on 0 1 10/15/2010 10:58:28
2 glru_on 0 1 10/15/2010 11:21:32
2 initiate_affinity 74809 1 10/15/2010 13:27:44
[/sourcecode]

【非常感谢Arup NandaMichael Möller(M2)校对这篇blog,他们大量参与了我的讨论。但是,如果这篇文章中有什么错误那么全是我个人的责任。】
这篇文章的pdf格式可以此Dynamic_remastering_RAC_orainternals下载.

One thought on “RAC Object Remastering (Dynamic Remastering)

  1. Kamus says:

    译者注:
    _gc_affinity_limit和_gc_affinity_minimum参数的设置不用重新启动实例就可以生效,而设置_gc_affinity_time=0和_gc_undo_affinity=FALSE则必须重新启动实例。因此如果你的数据库正在经历DRM的折磨,而你又无法重新启动数据库,那么尝试设置:
    alter system set “_gc_affinity_limit”=10000000;
    alter system set “_gc_affinity_minimum”=10000000;

Leave a Reply

Your email address will not be published. Required fields are marked *