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のダンプを取得する。
- PDB1のKSHINKUB.DUALテーブルにSQLを実行
- PDB2のKSHINKUB.DUALテーブルに上記と同一のSQLを実行
- コンテナ・データベース上で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へ
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の被害を受けると、その影響はインスタンスダウンにもつながる重大なものになる。
また、コンテナ・データベースの運用における、全プラガブル・データベースのダウンといった危険性は、リソース制御といった観点とは若干異なるので、ここでは述べないが、今後、十分な検証が必要であると思う。
各種ラッチのリソース不足も見過ごせない
筆者の環境では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つのデータベースに全てを集約するのではなく、ある程度の単位、例えば、本番OLTP系システム、本番DW系システム、開発系システム等の単位でコンテナ・データベースを分ける方が良いのではないかと思う。
ある程度分けられたコンテナ・データベースの中にいくつかのプラガブル・データベースを構築することで、ワークロードの分離やサービスレベルの維持など柔軟な運用が可能になると思う。
本番OLTP系システムにはインスタンス・ケージングでCPUを全体の30%割り当て、Memoryはデータベース内のMEMORY_TARGETで全体の60%を割り当てる。同様に本番DW系システム、開発系システムにはCPUをそれぞれ、60%、10%、Memoryを30%、10%といった具合だ。その後、各プラガブル・データベースにはCDBリソースプランを適用していく。
ここまでくると、I/OリソースもMemoryリソース同様に制御しなくていけない事は明白だが、次回でI/Oリソースの制御について考えてみたい。
コメント
コメントを投稿