スキップしてメイン コンテンツに移動

12cでリソースの共有と非共有のはざまで... その2

Memoryリソース編

 前回CPUリソース編として、Oracle Database 12cのマルチテナント・アーキテクチャにおけるCPUリソースの制御の様子を検証してみた。今回は、Oracle Databaseで重要なコンポーネントであるSGA(System Global Area)を含むMemoryリソースの制御の様子を見てみたい。

 Oracle Databaseはインスタンス単位でSGAをもち、SGA内のコンポーネント(つまりMemoryリソース)の配分は、DBAが手動もしくは、Oracle Databaseによる自動管理で行うことが従来より可能であった。ここでのポイントは、Memoryリソースはインスタンス単位である点であり、プラガブル・データベース単位での制御はないということである。

 結論を先に書くと、Database Resource Managerや従来のDBAによる手動もしくはOracle Databaseによる自動によるMemory管理では、プラガブル・データベース毎にMemoryリソースを制御することは不可能である。

 本検証では、Memoryリソース管理が不可能である点を踏まえ、マルチテナント・アーキテクチャを考える際に注意しなければいけない(であろう)事を検証してみる。


 まず、マルチテナント・アーキテクチャにおけるSGAの仕組みを簡単に見てみる。


 この図から、SGAはコンテナ・データベースが管理し、プラガブル・データベース固有のSGAは存在しないことが分かる。

 さらに、重要なのは従来からSGAコンポーネントの中での鬼門であったSHARED POOLもコンテナ・データベースに1つしか存在しないという事をである。これは、1つのプラガブル・データベースでSHARED POOLの枯渇を誘発するような処理(例えばバインド変数を使用しないSQLが大量に実行されている等)により、他の問題のないプラガブル・データベースでエラー(ORA-4031など)が発生するといった悪影響に関する懸念が残る。

 今回は、このSHARED POOL、特にLIBRARY CACHE周りの動きを少し検証してみようと思う。

LIBRARY CACHE内のカーソルは誰のもの?

 SHARED POOL内のコンポーネントの中でも、SQL文の解析情報を格納するLIBRARY CACHEは特に重要なコンポーネントになるわけだが、そもそも、マルチテナント・アーキテクチャをとる場合、このLIBRARY CACHEにまつわる管理方法は従来とどう変わったのか少し見ておきたい。

 カーソルが誰のものか確認するため、いくつかSQLを実行して、その後のLIBRARY CACHEのダンプを取得する。

  1. PDB1のKSHINKUB.DUALテーブルにSQLを実行
  2. PDB2のKSHINKUB.DUALテーブルに上記と同一のSQLを実行
  3. コンテナ・データベース上でLIBRARY CACHEのダンプを取得
 以下に取得したダンプの抜粋を記載する。


Bucket: #=68414 Mutex=0x10540ea4d8(270582939648, 6, 0, 6)
  LibraryHandle:  Address=0x1067379978 Hash=5aa90b3e LockMode=N PinMode=0 LoadLockMode=0 Status=VALD
    ObjectName:  Name=select /* Tokuno JPOUG */ 1 from dual
      FullHashValue=4a990487ce5893eba2a8a5285aa90b3e Namespace=SQL AREA(00) Type=CURSOR(00) ContainerId=1 ContainerUid=0 Identifier=1521027902 OwnerIdn=106
    Statistics:  InvalidationCount=0 ExecutionCount=2 LoadCount=3 ActiveLocks=1 TotalLockCount=2 TotalPinCount=1
    Counters:  BrokenCount=1 RevocablePointer=1 KeepDependency=2 Version=0 BucketInUse=1 HandleInUse=1 HandleReferenceCount=0
    Concurrency:  DependencyMutex=0x1067379a28(0, 2, 0, 0) Mutex=0x1067379ac0(63, 33, 0, 6)
    Flags=RON/PIN/TIM/PN0/DBN/[10012841]
    WaitersLists:
      Lock=0x1067379a08[0x1067379a08,0x1067379a08]
      Pin=0x10673799e8[0x10673799e8,0x10673799e8]
      LoadLock=0x1067379a60[0x1067379a60,0x1067379a60]
    Timestamp:  Current=08-12-2013 21:59:15
    HandleReference:  Address=0x1067379b58 Handle=(nil) Flags=[00]
    ReferenceList:
      Reference:  Address=0x1066f3d6e8 Handle=0x1016c8c820 Flags=ROD[21]
      Reference:  Address=0x1066e9c730 Handle=0x10673f07c8 Flags=ROD[21]
    LibraryObject:  Address=0x10673f0a88 HeapMask=0000-0001-0001-0000 Flags=EXS[0000] Flags2=[0000] PublicFlags=[0000]
      DataBlocks:
        Block:  #='0' name=KGLH0^5aa90b3e pins=0 Change=NONE
          Heap=0x1016c8c640 Pointer=0x10673f0b50 Extent=0x10673f09f8 Flags=I/-/P/A/-/-
          FreedLocation=0 Alloc=3.187500 Size=3.976562 LoadTime=4537161860
      ChildTable:  size='16'
        Child:  id='0' Table=0x10673f1920 Reference=0x10673f1388 Handle=0x106735bc10
        Child:  id='1' Table=0x10673f1920 Reference=0x10673f1658 Handle=0x106744c510

 上記は親カーソルに該当する部分のダンプとなるが、3行目のContainerId=1に注目してもらいたい。親カーソルは常にCDB$ROOTがオーナーになっている。さらに、最終行付近のChildTableでは、関連する子カーソルが2つ存在することが示されている。

1番目の子カーソルのダンプ

Child:  childNum='0'
          LibraryHandle:  Address=0x106735bc10 Hash=0 LockMode=N PinMode=0 LoadLockMode=0 Status=VALD
            Name:  Namespace=SQL AREA(00) Type=CURSOR(00) ContainerId=4
            Statistics:  InvalidationCount=0 ExecutionCount=1 LoadCount=1 ActiveLocks=1 TotalLockCount=1 TotalPinCount=2
            Counters:  BrokenCount=1 RevocablePointer=1 KeepDependency=0 Version=0 BucketInUse=0 HandleInUse=0 HandleReferenceCount=0
            Concurrency:  DependencyMutex=0x106735bcc0(0, 0, 0, 0) Mutex=0x1067379ac0(63, 33, 0, 6)
            Flags=RON/PIN/PN0/EXP/CHD/[10012111]
            WaitersLists:
              Lock=0x106735bca0[0x106735bca0,0x106735bca0]
              Pin=0x106735bc80[0x106735bc80,0x106735bc80]
              LoadLock=0x106735bcf8[0x106735bcf8,0x106735bcf8]
            ReferenceList:
              Reference:  Address=0x10673f1388 Handle=0x1067379978 Flags=CHL[02]
            LibraryObject:  Address=0x1067405ba0 HeapMask=0000-0001-0001-0000 Flags=EXS[0000] Flags2=[0000] PublicFlags=[0000]
              Dependencies:  count='4' size='16' table='0x10674069c8'
                Dependency:  num='0'
                  Reference=0x1067406210 Position=0 Flags=DEP[0001]
                  Handle=0x10575b4a98 Type=NONE(255) Parent=PDB3.KSHINKUB
                Dependency:  num='1'
                  Reference=0x1067406260 Position=33 Flags=DEP[0001]
                  Handle=0x1056b7cab0 Type=CURSOR(00) Parent=PDB3.KSHINKUB.DUAL
                Dependency:  num='2'
                  Reference=0x10674062a0 Position=33 Flags=DEP[0001]
                  Handle=0x101734c0d8 Type=SYNONYM(05) Parent=PDB3.PUBLIC.DUAL
                Dependency:  num='3'
                  Reference=0x10674062e0 Position=33 Flags=DEP[0001]
                  Handle=0x105744ff30 Type=TABLE(02) Parent=PDB3.SYS.DUAL
              ReadOnlyDependencies:  count='1' size='16'
                ReadDependency:  num='0' Table=0x1067406a60 Reference=0x1067406110 Handle=0x10673f07c8 Flags=DEP/ROD/KPP[61]
              Accesses:  count='1' size='16'
                Dependency:  num='3' Type=0009
              Translations:  count='1' size='16'
                Translation:  num='0' Original=0x101734c0d8 Final=0x105744ff30
              DataBlocks:
                Block:  #='0' name=KGLH0^5aa90b3e pins=0 Change=NONE
                  Heap=0x1066eac258 Pointer=0x1067405c68 Extent=0x1067405b10 Flags=I/-/P/A/-/-
                  FreedLocation=0 Alloc=2.750000 Size=3.937500 LoadTime=4537161860
                Block:  #='6' name=SQLA^5aa90b3e pins=0 Change=NONE
                  Heap=0x10673f1168 Pointer=0x103f390598 Extent=0x103f38f978 Flags=I/-/-/A/-/E
                  FreedLocation=0 Alloc=4.828125 Size=7.898438 LoadTime=0
            NamespaceDump:
              Child Cursor:  Heap0=0x1067405c68 Heap6=0x103f390598 Heap0 Load Time=08-12-2013 21:59:15 Heap6 Load Time=08-12-2013 21:59:15

2番目の子カーソルのダンプ

Child:  childNum='1'
          LibraryHandle:  Address=0x106744c510 Hash=0 LockMode=0 PinMode=0 LoadLockMode=0 Status=VALD
            Name:  Namespace=SQL AREA(00) Type=CURSOR(00) ContainerId=3
            Statistics:  InvalidationCount=0 ExecutionCount=1 LoadCount=1 ActiveLocks=0 TotalLockCount=1 TotalPinCount=2
            Counters:  BrokenCount=1 RevocablePointer=1 KeepDependency=0 Version=0 BucketInUse=0 HandleInUse=0 HandleReferenceCount=0
            Concurrency:  DependencyMutex=0x106744c5c0(0, 0, 0, 0) Mutex=0x1067379ac0(63, 33, 0, 6)
            Flags=RON/PIN/PN0/EXP/CHD/[10012111]
            WaitersLists:
              Lock=0x106744c5a0[0x106744c5a0,0x106744c5a0]
              Pin=0x106744c580[0x106744c580,0x106744c580]
              LoadLock=0x106744c5f8[0x106744c5f8,0x106744c5f8]
            ReferenceList:
              Reference:  Address=0x10673f1658 Handle=0x1067379978 Flags=CHL[02]
            LibraryObject:  Address=0x106731a248 HeapMask=0000-0001-0001-0000 Flags=EXS[0000] Flags2=[0000] PublicFlags=[0000]
              Dependencies:  count='4' size='16' table='0x106731b070'
                Dependency:  num='0'
                  Reference=0x106731a8b8 Position=0 Flags=DEP[0001]
                  Handle=0x10576ab810 Type=NONE(255) Parent=PDB2.KSHINKUB
                Dependency:  num='1'
                  Reference=0x106731a908 Position=33 Flags=DEP[0001]
                  Handle=0x10673c7e30 Type=CURSOR(00) Parent=PDB2.KSHINKUB.DUAL
                Dependency:  num='2'
                  Reference=0x106731a948 Position=33 Flags=DEP[0001]
                  Handle=0x1066ea55b8 Type=SYNONYM(05) Parent=PDB2.PUBLIC.DUAL
                Dependency:  num='3'
                  Reference=0x106731a988 Position=33 Flags=DEP[0001]
                  Handle=0x1024f289e8 Type=TABLE(02) Parent=PDB2.SYS.DUAL
              ReadOnlyDependencies:  count='1' size='16'
                ReadDependency:  num='0' Table=0x106731b108 Reference=0x106731a7b8 Handle=0x1016c8c820 Flags=DEP/ROD/KPP[61]
              Accesses:  count='1' size='16'
                Dependency:  num='3' Type=0009
              Translations:  count='1' size='16'
                Translation:  num='0' Original=0x1066ea55b8 Final=0x1024f289e8
              DataBlocks:
                Block:  #='0' name=KGLH0^5aa90b3e pins=0 Change=NONE
                  Heap=0x1066e9c2c0 Pointer=0x106731a310 Extent=0x106731a1b8 Flags=I/-/-/A/-/-
                  FreedLocation=0 Alloc=2.750000 Size=3.937500 LoadTime=4537166160
                Block:  #='6' name=SQLA^5aa90b3e pins=0 Change=NONE
                  Heap=0x10673f14f8 Pointer=0x103f38e598 Extent=0x103f38d978 Flags=I/-/-/A/-/E
                  FreedLocation=0 Alloc=4.828125 Size=7.898438 LoadTime=0
            NamespaceDump:
              Child Cursor:  Heap0=0x106731a310 Heap6=0x103f38e598 Heap0 Load Time=08-12-2013 21:59:19 Heap6 Load Time=08-12-2013 21:59:19


 子カーソルのダンプから親カーソル同様に、ContainerIdを見ると実行されたプラガブル・データベースがオーナーとなっていることが分かる。さらに、カーソルに依存するオブジェクトとして[プラガブル・データベース名].[スキーマ名].[オブジェクト名]という表記で存在することも分かる。

 つまり、(誤解を恐れずに言うと)マルチテナント・アーキテクチャにおいて、プラガブル・データベースで実行されるSQL(カーソル)は従来のスキーマの拡張として扱われていることになる。


 ちなみに、この時、コンテナ・データベース上でV$SQL_SHARED_CURSORで子カーソルが生成された理由を見るとHASH_MATCH_FAILEDとなっていた。マニュアルによれば、「既存の子カーソルに、現在のカーソルに必要な安全でないリテラル・バインド・ハッシュ値がない」という何とも意味不明な原因なのだが、これは、CURSOR_SHARING時にリテラルをバインド変数に変換しようとしたが、そのリテラルが安全にバインド変数化できない可能性があるので、そのまま(新規に子カーソルを作成して)実行した。といった場合に多く見られる。今回のマルチテナント・アーキテクチャで、AUTH_CHECK_MISMATCHやTRANSLATION_MISMATCHではなく、HASH_MATCH_FAILEDとなるのは興味深い。

 親カーソルはコンテナ・データベースをオーナーとしてプラガブル・データベース間で共有するが、子カーソルはプラガブル・データベースをオーナーとして別スキーマで実行されたという扱いに見える。マルチテナント・アーキテクチャのMemory管理は、従来と同じ(ような)枯れたアーキテクチャであり、今まで通りの信頼性が担保できそうだが、逆に、従来から問題だった点にも十分注意が必要だという事だと思う。

 冒頭、ORA-4031の危険性を述べたが、クラウドでのマルチテナント・アーキテクチャを考えた時、1つのプラガブル・データベースの挙動で、コンテナ・データベース内の全てのプラガブル・データベースの動作に影響を与える可能性がある。次に、実際の1つのプラガブル・データベースでSHARED POOL不足を発生させ、その影響を見てみる事にする。


いざ、ORA-4031へ


 今回は、SHARED POOL不足(ORA-4031)を発生させやすい状況を作るため、プラガブル・データベースを2つもつコンテナ・データベースの初期化パラメーターMEMORY_TARGETを4GBと設定した。1つのコンテナ・データベースで以下のコードを実行する。



create or replace procedure proc_4031(p_depth in number, p_com in number default 1)
is
 v_cursor sys_refcursor;
 v_sql  varchar2(30000);
begin
 v_sql := 'select /* '||p_com||' */ 1 ' || rpad(' ', 4000) || rpad(' ', 4000) || rpad(' ', 4000) || rpad(' ', 4000) || rpad(' ', 4000) || rpad(' ', 4000) || rpad(' ', 4000) || 'from dual a_' || p_depth;
 open v_cursor for v_sql;
 proc_4031(p_depth+1, p_com);
end;
/

alter system set open_cursors = 65535 scope=memory;

exec proc_4031(1)

 上記のPL/SQLを実行すると運が良ければ(?)、SHARED POOLが枯渇しORA-4031が発生する。ここで、別のプラガブル・データベースでも同様にORA-4031が発生することが確認できる。(ただし、タイミングに依存する)

 この別プラガブル・データベースによる不安定な処理(SHARED POOL不足を誘発する処理)の影響は、他のプラガブル・データベースだけにとどまらないであろうことも、先のメモリー構造を見れば予測できる。つまり、コンテナ・データベースにも影響は波及するであろうということである。

 1つのプラガブル・データベースが1つのSGA内のコンポーネントを大量に確保している場合、仮にコンテナ・データベースのバックグランド・プロセスがORA-4031の被害を受けると、その影響はインスタンスダウンにもつながる重大なものになる。

 また、コンテナ・データベースの運用における、全プラガブル・データベースのダウンといった危険性は、リソース制御といった観点とは若干異なるので、ここでは述べないが、今後、十分な検証が必要であると思う。

各種ラッチのリソース不足も見過ごせない


 Oracle Database上では、SGAの共有リソースの排他制御のために各種ラッチを使って、そのリソースの同時実行を制御している。今まで述べてきたSHARED POOLの場合、shared pool latchを使ってSHARED POOL上のオブジェクトの排他制御をしている。このshared pool latchの数はCPU数やSHARED POOLのサイズにより異なるが有限のリソースとなる。

 筆者の環境ではshared pool latchは4つとなっていた。


SQL> col param for a20
SQL> col sessionval for a10
SQL> col instanceval for a10
SQL> col descr for a30
SQL>
SQL> SELECT a.ksppinm Param
  2        ,b.ksppstvl SessionVal
  3        ,c.ksppstvl InstanceVal
  4        ,a.ksppdesc Descr
  5  FROM   x$ksppi a ,
  6         x$ksppcv b ,
  7         x$ksppsv c
  8  WHERE  a.indx = b.indx AND
  9         a.indx = c.indx AND
 10         a.ksppinm like '/_kghdsidx_count' escape '/'
 11  ORDER BY 1;

PARAM                SESSIONVAL INSTANCEVA DESCR
-------------------- ---------- ---------- ------------------------------
_kghdsidx_count      4          4          max kghdsidx count


 この有限のリソースを使ってデータベース内のオブジェクトを管理している点も従来のデータベースと同じである。しかし、マルチテナント・アーキテクチャは多くのプラガブル・データベースを1つのコンテナ・データベースに集約することを目的の一つにしているにも関わらず、ラッチのリソースの上限は従来と変わらない構造となっている。これらラッチのリソース不足も懸念材料の一つだと思われる。

Memoryリソースを考慮したコンテナ・データベースの設計


 マルチテナント・アーキテクチャに限った事ではないが、データベースを集約して1つのインスタンスに多様なワークロードをかける場合、Memoryリソースの取り扱いには注意が必要だ。特にマルチテナント・アーキテクチャの場合、データベースの集約が大きな目的の1つになるので、これまで以上に多用なワークロードが発生する事を想定しておく必要がある。

 筆者は、1つのデータベースに全てを集約するのではなく、ある程度の単位、例えば、本番OLTP系システム、本番DW系システム、開発系システム等の単位でコンテナ・データベースを分ける方が良いのではないかと思う。

 ある程度分けられたコンテナ・データベースの中にいくつかのプラガブル・データベースを構築することで、ワークロードの分離やサービスレベルの維持など柔軟な運用が可能になると思う。

 本番OLTP系システムにはインスタンス・ケージングでCPUを全体の30%割り当て、Memoryはデータベース内のMEMORY_TARGETで全体の60%を割り当てる。同様に本番DW系システム、開発系システムにはCPUをそれぞれ、60%、10%、Memoryを30%、10%といった具合だ。その後、各プラガブル・データベースにはCDBリソースプランを適用していく。

 ここまでくると、I/OリソースもMemoryリソース同様に制御しなくていけない事は明白だが、次回でI/Oリソースの制御について考えてみたい。

コメント