latch shared pool

概要

共有プール・ラッチは共有プールの基本的なメモリー構造であるヒープを保護する役割をします。 空きチャンクを探すために空きリストを探索して、適切なチャンクを割り当てて、必要な場合空きチャンクを分割する、という一連の作業は全て共有プール・ラッチを獲得してから 可能になります。共有プール・ラッチを獲得する時、競合が発生するとlatch:shared pool待機イベントで待機します。

ヒープと関連した一連の作業は非常に早く完了するので、普通の場合は共有プール・ラッチでの競合は発生しません。 しかし、次のような場合には、latch:shared pool待機イベントが増加する可能性があります。

  • 共有プール・ラッチは全体インスタンスに一つだけ存在します。 一つの共有プールでは一つの共有プール・ラッチが使われますが、 これは共有プールの基本メモリー構造であるヒープのアーキテクチャーによるものです。 従って、同時に複数のセッションにチャンクを割当てなければならない場合、 一つの共有プール・ラッチを獲得するため競合が発生するようになります。
  • ハード解析の発生が激しい場合は、チャンクが分割される現象が頻繁に発生します。 これによって空きリストに数多くの小さな空きチャンクができる現象が発生します。このような現象を共有プールの断片化と言います。 共有プールの断片化によって空きリストを探索する時間が長くなり、その分共有プール・ラッチを保有する時間が増えます。 共有プールの断片化は共有プール・ラッチ競合を発生させる根本的な原因であり、ORA-4031エラーを発生させる原因でもあります。

ハード解析が発生すると、新しいSQL情報を入れるチャンクを割当られるために空きリストを探索しなければなりません。 このような一連の作業が行われる時、ただ一つのプロセスだけが共有プール・ラッチを占有するため、 同時に数多くのセッションがハード解析を実行する場合は、latch:shared pool待機イベントが発生します。

ハード解析が過度に発生するシステムで共有プール・ラッチ待機イベントを減らすために共有プールのサイズを増やすのは非常に危険な発想です。 共有プールが大きくなると、それだけの空きリスト数と空きリストで管理しなければならない空きチャンクの数も増えるためです。 従って、空きリストを探索するために共有プール・ラッチを獲得する時間が長くなります。 それに伴い、latch:shared pool待機イベントの待ち時間も増えていきます。

待機パラメータと待機時間

待機パラメータ

  • P1:ラッチのアドレス
  • P2:ラッチ番号(V$LATChNAME.LATCH#と同一)。ラッチ番号に該当するラッチ名を確認するためには、下記のSQLを実行します。
SELECT * FROM v$latchname WHERE latch# = &p2_value;
  • P3:試行回数。ラッチを獲得するため、プロセスがトライした回数。

待機時間

待機時間は、指数的に増加します。

チェックポイントとソリューション

サブ・プールを使用する

Oracle9i以降からは、共有プールをいくつかのサブ・プール(最大7個まで)で分けて管理することができます。 _KGHDSIDX_COUNT隠しパラメータを利用すればサブ・プール数を管理することができます。 OracleはCPU数が4以上で、共有プールの大きさが250MB以上の場合、_KGHDSIDX_COUNT値の数だけサブ・プールを生成し、共有プールを管理します。 サブ・プールは独立的な共有プールとして管理されており、独自の空きリスト、LRUリスト、共有プール・ラッチを持ちます。 従って、共有プールが大きい場合にはサブ・プールを分けて管理することによって共有プール・ラッチ競合を減らすことができます。

共有プールのサイズを小さくする

ハード解析によって共有プール・ラッチ競合が発生する場合、他の解決策としては共有プールのサイズを小さくすることがあります。 共有プールの領域が減った分、空きリストの空きチャンク数も減少し、空きリスト探索に所要する時間も短くなるためです。 しかし、この方法を使った場合、ORA-4031エラーが発生する可能性が高くなります。また、共有プールにキャッシュできるオブジェクト数が減り、 追加のハード解析が多発する可能性があります。

これを解消するためには、DBMS_SHARED_POOL.KEEPプロシージャを利用し、頻繁に使われるSQLカーソルやパッケージ、プロシージャなどを 共有プールに永続的にキャッシュさせる方法があります。 DBMS_SHARED_POOL.KEEPで指定したオブジェクトは共有プールに永続にキャッシュされるため、 ALTER SYSTEM FLUSH SHARED_POOLコマンドを使っても共有プールからフラッシュされなくなります。

つまり、共有プールを小さくすることと同時にDBMS_SHARED_POOLパッケージを使って、 よく利用されるオブジェクトをメモリーにキャッシュするのも一つの方法になります。

共有カーソル機能を使用する

共有カーソル機能は、リテラルを使ったSQL文が自動でバインド変数を使うように置き換えて、カーソルを共有させる機能です。 共有カーソル機能は既存のリテラルSQLをバインド変数に変えることができない場合推奨されます。 この機能を使う場合、既存SQL文の実行計画が変更される可能性もあるため、十分なテストを行ってから適用することが望ましいです。

豆知識

共有プールの構造

共有プールはSGAのコンポーネントの一つです。 下記のコマンドでSGAのコンポーネントを確認することができます。

SQL> show sga
Total System Global Area          524288000 bytes
Fixed Size                          2007624 bytes
Variable Size                     142609848 bytes
Database Buffers                  373293056 bytes
Redo Buffers                        6377472 bytes

共有プールはSGA領域の中で、可変する領域に属します。 可変領域は共有プール、Java プール、ラージ・プール、ストリーム・プールで構成されています。 また共有プールは様々な種類のメモリー領域に分けられます。代表的なものは、ライブラリ・キャッシュとディクショナリ・キャッシュです。 共有プールがどんな領域に分けられているのかはV$SGASTATビューで確認することができます。

SQL> select count(*) from v$sgastat where pool = 'shared pool';

  COUNT(*)
----------
       599     --> 599個のメモリー領域に分けられる。

-- 大きいの順で20個だけ問い合わせてみると下記の結果になる。 
--各メモリー領域の大きさは動的に変わる。
SQL> select * from (
 select name, bytes from v$sgastat
 where pool = 'shared pool'
 order by bytes desc
 ) where rownum <= 20;


NAME                                BYTES
------------------------------ ----------
free memory                      38515152
ASH buffers                       8388608
sql area                          8074192
row cache                         7480368
library cache                     5792120
kglsim hash table bkts            4194304
KCB Table Scan Buffer             3981120
KSFD SGA I/O b                    3977064
CCursor                           2586248
private strands                   2396160
db_block_hash_buckets             2342912
PL/SQL MPCODE                     2261672
KQR M PO                          1860816
kks stbkt                         1572864
event statistics per sess         1555840
KGLS heap                         1485744
FileOpenBlock                     1447104
PCursor                           1339640
KTI-UNDO                          1286064
PL/SQL DIANA                      1282376

共有プールの構成要素は大きく分けて、次のように区分されます。

  • 永続領域 : プロセス・リスト、セッション・リスト、エンキュー・リスト、トランザクション・リストなどの リソースは共有プールの永続領域に割り当てられます。
  • ライブラリ・キャッシュ : SQL文を実行するのに必要な全てのオブジェクト(SQL、表、ビュー、プロシージャなど)に関する情報を管理します。
  • ロー・キャッシュ : ディクショナリ・キャッシュとも言い、Oracleが使うディクショナリー情報を管理します。
  • 予約領域 : 動的なメモリー割当のためにOracleは予約領域を割り当てておきます。

共有プールはヒープと呼ばれるメモリー管理手法で管理されます。 共有プールのヒープからメモリーの割当てを要求する全てのプロセスは必ず共有プール・ラッチを獲得しなければなりません。 例えば、ハード解析が発生した場合、プロセスは共有プール内にSQL文を保存するメモリーを割当られるため、 必ず共有プール・ラッチを獲得します。 共有プール・ラッチは基本的にインスタンス全体で一つのみ存在しますが、 必要なメモリーが割当られるまで保有していなければなりません。 従って、同時に多くのプロセスが共有プール・メモリーを使おうとする場合は、 ラッチを獲得するための競合が発生します。この競合が発生すると、セッションはlatch:shared pool待機イベントで待機します。

Oracle9i以上からは共有プールをいくつかのサブ・プール(最大7個まで)に分けて管理することができます。 _KGHDSIDX_COUNT隠しパラメータを利用すればサブ・プール数を管理することができます。 OracleはCPU数が4以上で、共有プールの大きさが250MB以上の場合、 _KGHDSIDX_COUNT値の数だけサブ・プールを作って共有プールを管理します。 サブ・プールは独自の共有プールで管理されており、独自の空きリスト、 LRUリスト、共有プール・ラッチを持ちます。 従って、共有プールが大きい場合には、サブ・プールに分けて管理することをお勧めします。 こうすることで共有プール・ラッチ競合を減らすことができます。

ヒープとヒープ・ダンプ

ヒープに対する基本的な項目を整理してみます。

  • ヒープ・マネージャにより管理されている、Oracleの最も基本的なメモリー割当方法です。 ヒープ・マネージャはメモリー・マネージャが提供するサービスを利用し、動的にメモリーを管理します。 ライブラリ・キャッシュ・マネージャはヒープ・マネージャでライブラリ・キャッシュを管理します。
  • 共有プールは基本的に一つの最上位のヒープを持ち、最上位のヒープはまたいくつかのサブ・ヒープを含みます。 サブ・ヒープはまたさらにそれ自身のサブ・ヒープを含みます。 ヒープとサブ・ヒープは基本的に同じ構造を持ちます。 ヒープ内にある一つのチャンクは、サブ・ヒープに対してのポインタの役割をします。 Oracle9i以降からは適切な環境が整っていれば、様々な最上位のヒープを持つことができます。
  • ヒープはいくつかのエクステントをリンクされたリスト形式で持ちます。 一つのエクステントは物理的に一つのグラニュルを使います。 グラニュルとは、Oracle9i以降で使われている連続した物理メモリーの単位で、SGAの大きさが1GB未満であれば4MB、 1GB以上であれば16MBになります(Oracle10g基準)。一つのエクステントは複数のチャンクで構成されます。それぞれのチャンクの使用状況はX$KSMSPビューで確認することができます。 サブ・ヒープの場合、エクステントの大きさは変動します。 共有プールのメモリーはグラニュルを追加し、積み重ねていく形で割り当てられるため、ヒープと言われています。
  • チャンクのステータスは大きく分けて、free(直ちに使用可能)、recr(Recreatable:再生成可能)、 freeabl(Freeable:セッションやコールの間のみ必要なオブジェクトが使用している状態)、perm(Permanent:永続、再生成不可)になります。 freeあるいはrecr状態のチャンクは再使用が可能です。
  • ヒープは空きリストを管理するバケット表を持ちます。それぞれのバケットは、リンクされたリスト形式で空きチャンクに対する空きリストを持ちます。 一つのヒープには、合計255個のバケットが存在しており、バケットの空きチャンクの大きさはバケット番号の増加に伴って順次に増加します。
  • ヒープはLRUリストを持っています。 LRUリストは現在使用中でない、つまり再生成可能なチャンクのリストです。
  • 最上位のヒープは予約空きリストという別の空きリストを管理します。 予約空きリストは大きいオブジェクトを保存する共有プール内の予約領域に関する空きリスト情報を管理します。

ヒープ・ダンプを取ってみると、ヒープの構造を確認することができます。

下記のコマンドを実行し、USER_DUMP_DESTにヒープ領域に関するダンプ・ファイルを確認します。

SQL> alter session set events 'immediate trace name heapdump level 2';

次は、結果ファイルの内容です。

******************************************************
HEAP DUMP heap name="sga heap"  desc=c000000002e15030<-- 最上位ヒープ
 extent sz=0xfe0 alt=216 het=32767 rec=9 flg=-126 opc=0
 parent=0000000000000000 owner=0000000000000000 nex=0000000000000000
 xsz=0x210

EXTENT 0 addr=c000000096000000    <-- エクステント0
  Chunk c000000096000058 sz=  2342824    free      "    " <-チャンク0
  ...
EXTENT 1 addr=c00000009ac00000
  Chunk c00000009ac00058 sz=       48  R-freeable  "reserved stoppe"
  ...
Total heap size    = 67107456

FREE LISTS:    <-- 空きリスト
 Bucket 0 size=32     <-- バケット
 Bucket 1 size=40
 ...
 Bucket 7 size=88
  Chunk c00000009dffffa8 sz=       88    free      "   " <-空きチャンク
 Bucket 8 size=96
   ...
 Bucket 254 size=65560<-- 最大255個のバケット、Max(size of Bucket)=64KByte
   Chunk c0000000963ddfe0 sz=   139296    free      "               "
  ...
Total free space   =  6571496

RESERVED FREE LISTS:         <--予約空きリスト
 Reserved bucket 0 size=32
 ...
 Reserved bucket 13 size=65560
  Chunk c00000009ac00088 sz=   212808  R-free      "               "
  ...
Total reserved free space   =  3192120

UNPINNED RECREATABLE CHUNKS(lru first):<--LRUリスト : 空きリストを全部
                                        使い切ってしまうと、LRUリストを使用する。
SEPARATOR
Unpinned space     =        0  rcr=0 trn=0

PERMANENT CHUNKS:		<-- 永続チャンク
  Chunk c00000009623c000 sz=  1712096    perm      "perm"  alo=1168888
  ...
Permanent space    = 57342400
****************************************************** 

上記のようにヒープ・ダンプを取ることによって、ヒープの正確な構造情報を得ることができます。 各ヒープはまたサブ・ヒープ構造を持ちます。 ヒープは自分が保持している特定のチャンクにサブ・ヒープのアドレスを保存してサブ・ヒープの位置を管理します。 ヒープ・ダンプで分かるように、次のような”ds”値が設定されているチャンクがサブ・ヒープの位置を保存しているチャンクです。

Chunk c00000009a6f2800 sz=1112 freeable "CCursor" ds=c0000000993293e8

ds値はヒープ・ディスクリプタという意味であり、ds=c0000000993293e8がサブ・ヒープのアドレスを示します。 下記のコマンドで、該当サブ・ヒープのダンプを取ることができます。

SQL> alter session set events 'immediate trace name heapdump_addr addr
     0xc0000000993293e8';

下記のサブ・ヒープのダンプ内容を確認すると、ヒープ・ダンプとほとんど同じ形式を持っていることが分かります。 エクステントの大きさが変動する点と、予約空きリストを管理しないという点を除けば、サブ・ヒープは最上位のヒープと同じ構造を持ちます。

******************************************************
HEAP DUMP heap name="CCursor"  desc=c0000000993293e8
 extent sz=0x440 alt=32767 het=32 rec=9 flg=2 opc=0<-エクステントの大きさが変動
parent=c000000002e15030 owner=c00000009a6f3110 nex=0000000000000000 xsz=0x440
EXTENT 0 addr=c00000009a6f2818  <--エクステント
  Chunk c00000009a6f2828 sz=      504    perm      "perm        "  alo=96
  Chunk c00000009a6f2a20 sz=      384    free        "               "
  Chunk c00000009a6f2ba0 sz=       32     freeable  "kksfbc:hash1   "
  Chunk c00000009a6f2bc0 sz=      152    freeable  "kgltbtab       "
EXTENT 1 addr=c00000009a6f2c70
  Chunk c00000009a6f2c80 sz=     1072    perm      "perm        "  alo=1040
EXTENT 2 addr=c00000009a6f30e0
  Chunk c00000009a6f30f0 sz=      776     perm      "perm        "  alo=776
  Chunk c00000009a6f33f8 sz=      240     perm      "perm        "  alo=232
  Chunk c00000009a6f34e8 sz=       32     free        "               "
Total heap size    =     3192
FREE LISTS:   <--空きリスト
 Bucket 0 size=0
  Chunk c00000009a6f34e8 sz=       32     free       "               "
  Chunk c00000009a6f2a20 sz=      384    free       "               "
Total free space   =      416
UNPINNED RECREATABLE CHUNKS (lru first): <--LRUリスト
PERMANENT CHUNKS:      Permanent Chunks
  Chunk c00000009a6f2828 sz=      504    perm      "perm        "  alo=96
  Chunk c00000009a6f33f8 sz=      240     perm      "perm        "  alo=232
  Chunk c00000009a6f2c80 sz=     1072    perm      "perm        "  alo=1040
  Chunk c00000009a6f30f0 sz=      776     perm      "perm        "  alo=776
Permanent space    =     2592
******************************************************

分析事例

ハード解析による性能低下現象分析ケースⅠ

アプリケーションをMaxGaugeで監視した結果ですが、 ハード解析によりlatch:shared pool待機イベントが発生していることが確認できます。

マックスゲージ画面

下の結果はV$SESSION_EVENTビューとV$SESSTATビューで待機現象と作業量を分析したもので、 latch:shared pool待機イベントが最も多く発生していることが確認することができます。 また、ハード解析の回数が全体解析の回数と同じ数値になるほどハード解析が頻繁に発生していることも確認できます。

実行結果 Type=EVENT, Name=latch: shared pool, Value=112(cs)

Type=EVENT, Name=latch: library cache, Value=72(cs)
Type=EVENT, Name=events in waitclass Other, Value=7(cs)
Type=EVENT, Name=library cache load lock, Value=3(cs)
Type=EVENT, Name=library cache pin, Value=3(cs)
Type=EVENT, Name=row cache lock, Value=1(cs)
Type=EVENT, Name=latch: row cache objects, Value=1(cs)
Type=EVENT, Name=latch: library cache pin, Value=0(cs)
Type=EVENT, Name=latch: library cache lock, Value=0(cs)
Type=EVENT, Name=latch: cache buffers chains, Value=0(cs)
Type=EVENT, Name=cursor: mutex S, Value=0(cs)
Type=EVENT, Name=enq: TX – row lock contention, Value=0(cs)
Type=EVENT, Name=buffer busy waits, Value=0(cs)
Type=STATS, Name=session pga memory max, Value=3325096
Type=STATS, Name=session logical reads, Value=42162
Type=STATS, Name=execute count, Value=40570
Type=STATS, Name=parse count (total), Value=40268
Type=STATS, Name=parse count (hard), Value=40063
Type=STATS, Name=parse time elapsed, Value=7195
Type=STATS, Name=redo size, Value=5692
Type=STATS, Name=session cursor cache hits, Value=409
Type=STATS, Name=sorts (memory), Value=199
Type=STATS, Name=redo entries, Value=11
Type=STATS, Name=user commits, Value=4
Type=STATS, Name=physical reads, Value=0
Type=STATS, Name=sorts (disk), Value=0



共有カーソル機能を使用することによってハード解析を避けることができます。 下記のようなコマンドでCURSOR_SHARINGパラメータ値をFORCEに変更すると、全てのリテラルSQLを自動で共有可能なSQLに変換することができます。

ALTER SYSTEM SET CURSOR_SHARING=FORCE SCOPE=MEMORY;

下の結果は共有カーソルを設定した後の性能測定結果です。 latch:shared pool待機イベントが解消されており、ハード解析の回数が大きく減少したことが確認できます。

実行結果
Type=EVENT, Name=events in waitclass Other, Value=6(cs)

Type=EVENT, Name=library cache load lock, Value=3(cs)
Type=EVENT, Name=library cache pin, Value=3(cs)
Type=EVENT, Name=cursor: mutex S, Value=0(cs)
Type=EVENT, Name=enq: TX – row lock contention, Value=0(cs)
Type=EVENT, Name=latch: library cache, Value=0(cs)
Type=EVENT, Name=buffer busy waits, Value=0(cs)
Type=EVENT, Name=row cache lock, Value=0(cs)
Type=STATS, Name=session pga memory max, Value=2800808
Type=STATS, Name=execute count, Value=40391
Type=STATS, Name=session cursor cache hits, Value=40263
Type=STATS, Name=parse count (total), Value=40199
Type=STATS, Name=redo size, Value=4932
Type=STATS, Name=session logical reads, Value=1655
Type=STATS, Name=parse time elapsed, Value=151
Type=STATS, Name=sorts (memory), Value=119
Type=STATS, Name=parse count (hard), Value=52
Type=STATS, Name=redo entries, Value=16
Type=STATS, Name=user commits, Value=4
Type=STATS, Name=physical reads, Value=0
Type=STATS, Name=sorts (disk), Value=0
Type=TIME, Name=parse time elapsed, Value=190(cs)
Type=TIME, Name=hard parse elapsed time, Value=23(cs)
Type=TIME, Name=sql execute elapsed time, Value=5(cs)
Type=TIME, Name=DB time, Value=2(cs)

ハード解析による性能低下現象分析ケースⅡ

同時ユーザーが多いOLTPおよびWEB環境で、過剰なリテラルSQLの使用は、性能上の深刻な問題を引き起こす場合が多いです。 MaxGaugeを活用して、リテラルSQLの過度な使用による性能低下問題の原因を解析してみます。

性能低下区間の確認

性能問題が発生したインスタンスで収集された稼動ログから推移グラフを確認すると、「CPU」使用率には明確な変化が見えませんが、 10時17分をピークに「Active Session」および「Wait Events」が急増していることを簡単に確認できます。

■「CPU」使用率の推移グラフ

マックスゲージ画面

■「Active Session」数の推移グラフ

マックスゲージ画面

■「Wait Events」の推移グラフ(待機時間)

マックスゲージ画面

待機イベントの検出および分析

アクティブ・セッションの急増によるスローダウンの原因を究明するため、問題時点(10時17分)の待機イベントの発生内容を確認してみます。 MaxGaugeの「Stat/Wait」で同じ時点のトップ待機イベントを確認した結果、アイドル・イベント(SQL*Net message from client)以外のトップ待機イベントは latch freeであることが分かりました。

下図では、アクティブ・セッションの急増とlatch free待機イベントの関連性を確認するために、発生パターンを比較してみました。 その結果、latch free待機イベントは「Active Session」の推移と非常に似ていることが分かります。 また、latch free待機イベントは、問題が発生した時点の待機時間の約74%(全体392.53秒中、291.91秒)を占めています。 このようなパターンの比較によって、アクティブ・セッションの急増は、latch free待機イベントの急激な発生と関連しているとのことが考えられます。

マックスゲージ画面

実際、同じ時点の詳細データである「セッション・リスト」でも、latch free待機イベントがトップ待機イベントになっており、 その中でもlatch free(shared pool)およびlatch free(library cache)待機イベントが多く発生していることが確認できます。

マックスゲージ画面

待機イベント発生原因の調査

latch free待機イベントの発生原因は様々ですが、一般的にlatch free (shared pool)とlatch free (library cache)待機イベントが 同時に発生した場合は、ハード解析がその原因である可能性が高いです。 その根拠を確認するため、parse count (hard)、parse time cpu、parse time elapsed統計値の推移を確認してみます。

マックスゲージ画面

推移グラフの分析結果、アクティブ・セッションが急増した時点で、parse time elapsed統計値が20,000 cs /sec(=200sec/sec)に高まっていて、 スローダウン現象が発生するその前(09時26分)のparse count (hard)統計値が200回/秒発生したことが確認できます。 つまり、前の時点の過度なハード解析が共有プールのメモリー断片化を引き起こして、これによってライブラリ・キャッシュでのSQL検索や新規SQLを登録する時に、 遅延が発生したと考えられます。

セッションおよびSQLの分析

ハード解析の実行が多かった09時00分~ 09時30分間で、parse count (hard)統計値が1秒に10回以上のセッションを検索した結果、 1秒に100回以上ハード解析を実行したセッションは、ほとんど「JDBC Thin Client」プログラムであることが確認できます。

過多にハード解析を実行しているセッションは、ほとんどが類似したSQLを使っているにも関わらず、バインド変数を使わずリテラルSQLであることが 分かりました。

マックスゲージ画面 マックスゲージ画面

結論

latch free待機イベントの急増によるアクティブ・セッションの急増。
                 ↓
リテラルSQLによるハード解析の過剰な実行がスローダウンの原因。

解決策

  1. リテラルSQLのバインド変数化
  2. Prepared Statementを使用することによってJDBC P/G内のリテラルSQLを排除