2013/12/25

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

I/Oリソース編


 これまで、マルチテナント・アーキテクチャにおけるCPUリソース、Memoryリソースの制御方法を考察してきたが最後にI/Oリソースの制御について考えてみる。

 Oracle Databaseは共有ディスクアーキテクチャーをとることから、従来からI/Oはボトルネックになりやすい重要なコンポーネントだった。マルチテナント・アーキテクチャにより、多くのインスタンスが集約されるとさらに、I/Oのリソース管理の重要性が増してくることは明らかである。

 さらに、初回のCPUリソース編でも述べたが、Database Resource ManagerではI/Oのリソース管理は提供されておらず(*1)I/Oリソース管理をするにはExadataによりI/O Resource Managerを使用する必要がある。

 筆者はExadataを検証環境として持ち合わせていないので、ここでExadata I/O Resource Managerの検証結果を記載できない。

 そこで、CPUリソース編で説明したOSネイティブなリソースマネージャ(*2)を使用したI/Oリソースの管理の方法を紹介したい。

(*1) Runaway SessionとしてのI/Oリソース管理は提供されている
(*2) Linuxのcgroupsと初期化パラメーターPROCESSOR_GROUP_NAMEを使用する


I/Oリソース制御としてPROCESSOR_GROUP_NAME


 初回のCPUリソース編で初期化パラメーターPROCESSOR_GROUP_NAMEを紹介したが、これはPROCESSORに限らずOSの持つcgroups(本件検証環境はOracle Linux 6.4 x86_64)の全ての機能を使用できる。


注意
cgroupsの機能をOracle Database 12cがどこまでサポートしているか明確なドキュメントはなく、現時点は検証目的としてPROCESSOR_GROUP_NAMEでcgroupsのI/O制御を行っている事に注意

 まず、I/Oリソースに関して全く制御していない状況で、I/Oの速度を計測してみる。I/Oの速度計測にはOracleが提供しているDBMS_RESOURCE_MANAGER.CALIBRATE_IOプロシージャを使用する

set serveroutput on

declare
 l_latency   integer;
 l_iops      integer;
 l_mbps      integer;
begin
 dbms_resource_manager.calibrate_io (
  num_physical_disks => 12, /* # of disks */
  max_latency => 10,  /* max latency */
  max_iops => l_iops,  /* I/O Ops/sec */
  max_mbps => l_mbps,  /* MBytes/sec */
  actual_latency => l_latency /* actual latency */
 );
 dbms_output.put_line ('I/O Ops/sec = ' || l_iops);
 dbms_output.put_line ('Actual Latency = ' || l_latency);
 dbms_output.put_line('MB/sec = ' || l_mbps);
end;
/

 上記のmax_iopsはDB_BLOCK_SIZEのI/Oサイズ読み込みでの最大IOPSを表示する。検証環境のDB_BLOCK_SIZEは8KBとなっている。
また、actual_latencyもmax_iops同様にDB_BLOCK_SIZEのI/Oサイズ読み込みでの平均レイテンシー(ミリ秒)を表示する。

 さらにmax_mbpsは、I/Oサイズ1MB読み込みでの最大スループット(MB/秒)を表示する。

 まず、ハードウェアのベースラインを確認するため、cgroupsでI/Oリソースの制限をかけていない状況でI/O速度を計測してみる。


 続いて、cgroupsでI/O制御を行ってみるが、cgroupsの場合、I/Oリソースの制御はblkioで行う。blkioでは様々なI/Oリソースの制御が可能だが、今回はblkio.throttle.read_bps_deviceで行う。cgroupsのblkioで様々な制御が可能だが、詳細はOSのドキュメントを参照してもらいたい。

 今回のI/Oスループット(bps)の制御には、デバイス毎に上限値となるスループット(バイト)を設定する必要がある。今回はASMを使って複数デバイスでディスクグループを作成しているので、"ターゲットとなるスループット/ASMのディスクグループを構成するデバイス数"を各デバイスに設定していくことになる。

 イメージだけつかんでもらうためにサンプルのスクリプトを記載しておく。



#!/bin/sh

GROUP_NAME=IQCDB02

echo PROCESSOR_GROUP_NAME
echo "  ${GROUP_NAME}"

#exit code
ExitSuccess=0
ExitError=1
ExitSQLError=3

function execSQL() {
 local sqlStmt=${1}
 local retCode=-1
 local SID=$(ps -ef | grep +ASM | grep -i pmon | awk {'print $8'} | sed -e 's/asm_pmon_//g')
 local EUSER=$(ps -eo "euser,args" | grep +ASM | grep -i pmon | awk '{print $1}')
 local resultSet=$(su - ${EUSER} <<- _END_OF_SQL_ | grep -v ^$
  export ORACLE_SID=${SID}
  export ORAENV_ASK=NO

  . oraenv <<- _EOF > /dev/null
  _EOF


  export LANG=C
  export NLS_LANG=American_America.us7ascii

  sqlplus -s -L / as sysasm
   set head off
   set feed off
   set echo off
   set lin 500
   set pages 1000
   set null #
   whenever sqlerror exit ${ExitSQLError}
   ${sqlStmt}
   exit
  _END_OF_SQL_
  retCode=${PIPESTATUS[0]}
 )

 local errCnt=$(echo "${resultSet}" | grep -c ^ORA-)

 if [[ ${errCnt} -ne 0 ]]
 then
  echo "${resultSet}" | grep ^ORA- | while read Line
  do
   echo ${Line}
  done
  retCode=${ExitSQLError}
 else
  echo "${resultSet}" | grep -v ^$
  retCode=${ExitSuccess}
 fi

 return ${retCode}
}

function getMajor() {
 local dev=`basename ${1}`
 cat /proc/partitions | grep -E "${dev}$" | awk '{print $1}'
}

function getMinor() {
 local dev=`basename ${1}`
 cat /proc/partitions | grep -E "${dev}$" | awk '{print $2}'
}

function calcBandwidth() {
 local disks=${1}
 expr ${LimitedBW} / ${disks}
}

function blkio_bps() {
 sqlStmt="select d.path
    from v\$asm_disk d, v\$asm_diskgroup g
    where g.group_number=d.group_number
    and g.name=upper('${DG}');"
 resultSet=`execSQL "${sqlStmt}"`

 echo blkio.throttle.read_bps_device
 REC_COUNT=`echo "${resultSet}" | wc -l`
 BW_DEV=`calcBandwidth ${REC_COUNT}`
 echo "  ${REC_COUNT} devices"
 echo "  ${BW_DEV} bytes/deivce"

 for asm in ${resultSet[@]}
 do
  dev=`readlink -f ${asm}`
  mjr=`getMajor ${dev}`
  mnr=`getMinor ${dev}`
  echo "${mjr}:${mnr} ${BW_DEV}" > /mnt/cgroup/${GROUP_NAME}/blkio.throttle.read_bps_device
 done
}

function cpus() {
 NODES=`cat /mnt/cgroup/cpuset.mems`
 CPU_LIST=$(echo $CPUS | awk -F"," '{$1=$1; print}')

 echo cpuset.cpus
 echo "  ${CPU_LIST}"
 echo ${CPU_LIST} > /mnt/cgroup/${GROUP_NAME}/cpuset.cpus
 echo cpuset.mems
 echo "  ${NODES}"
 echo ${NODES} > /mnt/cgroup/${GROUP_NAME}/cpuset.mems
}

DG=""
LimitedBW=""
CPUS=""
while getopts "d:b:c:" GETOPTS
do
 case ${GETOPTS} in
  "d")
   DG=${OPTARG}
   ;;
  "b")
   LimitedBW=${OPTARG}
   ;;
  "c")
   CPUS=${OPTARG}
   ;;
 esac
done

if [ ! -d /mnt/cgroup ]; then
 mkdir /mnt/cgroup
fi

if [ `mount | grep /mnt/cgroup | wc -l` -eq 0 ]; then
 mount -t cgroup cgroup /mnt/cgroup
 chown -R oracle:oinstall /mnt/cgroup
 if [ ! -d /mnt/cgroup/${GROUP_NAME} ]; then
  mkdir /mnt/cgroup/${GROUP_NAME}
  chown -R oracle:oinstall /mnt/cgroup/${GROUP_NAME}
 fi
fi

if [ ! -z ${CPUS} ]; then
 cpus
 if [ "${DG}" != "" -a  "${LimitedBW}" != "" ]; then
  blkio_bps
 fi
fi

 今回はASMLibを使わず、udevによるシンボリックリンクでデバイスを設定しているので、シンボリックリンク先のデバイスを探すような処理が入っている。環境により物理デバイス(/dev/sdaなど)のメジャー番号、マイナー番号を取得する処理に変更が必要。

 それでは、2つ存在するコンテナ・データベースの内IQCDB02のみ100MB/sでスループット制限をかける。先ほどのスクリプトを使用して、ASMのディスクグループ"DATA"に対して、100MB/sで制限し、CPUは全CPU(CPUIDを0番から15番、つまり全16CPUコア)を許可する設定を行う。

# ./io_cgroup_manager.sh -d data -b 104857600 -c 0-15
PROCESSOR_GROUP_NAME
  IQCDB02
cpuset.cpus
  0-15
cpuset.mems
  0
blkio.throttle.read_bps_device
  12 devices
  8738133 bytes/deivce
# su - oracle
$ export ORACLE_SID=IQCDB02
$ sqlplus / as sysdba
SQL> alter system set processor_group_name='IQCDB02' scope=spfile;
SQL> shutdown immediate
SQL> startup
SQL> alter pluggable database all open;

 ここで、PROCESSOR_GROUP_NAME='IQPDB02'を設定してあるコンテナ・データベースIQCDB02とPROCESSOR_GROUP_NAMEを設定していないコンテナ・データベースIQCDB01で先ほどのCALIBRATE_IOプロシージャを実行してI/Oリソースの状況を比較してみる。


 上記では、スループット制限を100MB/sと設定したが88MB/sという結果になった。これはASMがI/Oを各デバイスに分散するが、その際、I/Oが完全に均等にならない、または何らかのオーバーヘッドがある事が原因だと思われる。しかしながら、I/Oリソースを制御するという観点から、多少の誤差はあるものの十分機能していると考える。


I/Oリソースを制御するといった観点でのまとめ


 Linuxのcgroupsと初期化パラメーターRESOURCE_GROUP_NAMEでのI/Oリソース制御はインスタンス単位(本検証ではコンテナ・データベース単位)での制御となるが、データベースの設計次第で十分使えると思う。

 前回のMemoryリソース編でも述べたが、マルチテナント・アーキテクチャでは、ワークロード種別やサービスレベル別で複数のコンテナ・データベースを作成し、そのコンテナ・データベース毎にプラガブル・データベースを作成し、データベースの集約を図るのが、筆者の思うベストプラクティスである。

 今回のLinux cgroupsと初期パラメーターPROCESSOR_GROUP_NAMEを使用する場合、プラガブル・データベース単位でのI/Oリソースの制御はできない。しかし、DWH用コンテナ・データベースには高スループット、開発用コンテナ・データベースには低スループット等の設定が可能となる。これにより、完璧ではないが、最低限のI/Oリソースの制御が可能であると思う。

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リソースの制御について考えてみたい。

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

はじめに

 2013年のJPOUG Advent Calendarも今日で最後になりました。私のAdvent Calendarネタに関しては、若干、旬を過ぎた感は否めませんが、Oracle Database 12cに関するものにしようと思います。

 実は、Oracle Database 12cリリース時に某メディアに寄稿予定だったものを(いろいろ事情があってお蔵入りになっていたもの)この場を借りて、全3回でお送りしようと思います。

Oracle Database 12c マルチテナント・アーキテクチャについて

 Oracle Database 12cの"c"がCloudを意味するようになり、プライベートやパブリックを問わずデータベースを集約して効率良くデータベースの集積率を高めるようなアーキテクチャを備えた今、"c"であるためのポイントは、集約した結果として物理的なリソースをどのように適切に配分するのか?また、物理的なリソースを適切に配分できるのか?ということに尽きると思う。

 物理的なリソースとはデータベースが使用するCPUリソース、Memoryリソース、I/Oリソースとなるが、細かいことを言えば、データベース内部のラッチなども挙げられる。

 O
racle Database 12cでは、どのようなリソース管理が可能かについて検証していくつもりです。また、Oracle Database 12cのマルチテナント・アーキテクチャで、データベースにおける各種リソースの管理方法が旧来のリリースから変更されており、合わせてOracle Database 12cがどのようにリソースを管理しているかについても適宜、検証を行いたい。

 まず、簡単にマルチテナント・アーキテクチャをおさらいしておく。




 マルチテナント・アーキテクチャを簡単に理解すると、OSの持つリソースはコンテナ・データベースが管理し、プラガブル・データベースは、あたかも従来のスキーマのような振舞い(しかし、個別のデータベースとして隔離されて動作する)をしているように見える。

 プラガブル・データベースの追加は、スキーマ追加と同様にCPUやメモリーといった新たなリソースを確保する必要はない。必要な時に、必要なだけ、上位のデータベース(コンテナ・データベース)が用意する。

注意
誤解があるといけないが、マルチテナント・アーキテクチャとスキーマ・アーキテクチャとは全く異なるものであるが、ここでは、私の受ける印象として"似ている"という趣旨を記載しています。

 これにより、1つのコンテナ・データベースに多くのプラガブル・データベースが格納可能であり、従来のデータベースに対するメンテナンス(バックアップやパッチ適用など)の多くは1つのコンテナ・データベースに対して1回行えば良くなる。

 しかしながら、多くのプラガブル・データベースが混在する場合、OSおよびデータベース内の様々のリソースの使用方法を細かく制御する事が重要なキーワードとなる。




CPUリソース編

 データベース側でリソース制御を行うには、Resource Limit(Profile)やDatabase Resource Manager(Enterprise Editionが必須)がある。また、Database Resource Managerと初期化パラメーターCPU_COUNTを利用したインスタンス・ケージングがあるが、Oracle Database 12cのDatabase Resource Managerでは、プラガブル・データベース単位やサービス単位など、さらに細かい粒度でリソース制御が可能になっている。

 筆者の環境は複数のコンテナ・データベースがあり、さらにコンテナ・データベース内には複数のプラガブル・データベースが存在している。そのような環境では、コンテナ・データベース間でインスタンス・ケージングを利用し、さらにリソース制御下の各コンテナ・データベースで各プラガブル・データベースのCPUリソースを制御したいわけだが、インスタンス・ケージングとCDBリソースプランを同時に利用することで、柔軟なCPUリソース管理が可能になる。

 また、目新しいところでいけば、Linuxプラットフォームにおける初期化パラメーターPROCESSOR_GROUP_NAMEがある。これは、OSがもつネイティブなリソースマネージャ(LinuxでいうところのControl Groups(以下、cgroups))の設定をインスタンス全体に適用するものである。パラメーターがPROCESSOR_GROUP_NAMEということでCPUリソースのみを管理しているようだが、実際、OSのもつリソース管理機構全体を利用できる。これは、今後のI/Oリソースの制御の部分で詳しく述べたい。

 以下、Database Resource Managerのマルチテナント・アーキテクチャへの拡張部分と、初期化パラメーターPROCESSOR_GROUP_NAMEを使ったCPUリソースの制御方法と結果を見ていきたい。

Database Resource Managerを使用した場合

 コンテナ・データベース上で、各プラガブル・データベースのCPUリソースを制御するDatabase Resource ManagerとしてCDBリソースプランが追加された。CDBリソースプランで制御可能な項目を以下に示す。


  1. CPUリソースの相対的な使用量としてshares
  2. CPUリソースの絶対的な使用率(最大値)としてのutilization_limit
  3. パラレル処理時の絶対的な使用率としてのparallel_server_limit

 このCDBリソースプランを使って、コンテナ・データベース内の各プラガブル・データベースのCPUリソースを制御できる。

 以下は、コンテナ・データベース名IQCDB02における各プラガブル・データベースのCPUリソースを制御するサンプルとなる。

exec DBMS_RESOURCE_MANAGER.CREATE_PENDING_AREA();

BEGIN
  DBMS_RESOURCE_MANAGER.CREATE_CDB_PLAN(
    plan    => 'IQCDB02',
    comment => 'CDB resource plan for IQCDB02');
END;
/

BEGIN
  DBMS_RESOURCE_MANAGER.CREATE_CDB_PLAN_DIRECTIVE(
    plan                  => 'IQCDB02', 
    pluggable_database    => 'PDB02', 
    shares                => 7,
    utilization_limit     => 87.5,
    parallel_server_limit => 87.5);
END;
/

BEGIN
  DBMS_RESOURCE_MANAGER.CREATE_CDB_PLAN_DIRECTIVE(
    plan                  => 'IQCDB02', 
    pluggable_database    => 'PDB03', 
    shares                => 1,
    utilization_limit     => 12.5,
    parallel_server_limit => 12.5);
END;
/

exec DBMS_RESOURCE_MANAGER.VALIDATE_PENDING_AREA();
exec DBMS_RESOURCE_MANAGER.SUBMIT_PENDING_AREA();

ALTER SYSTEM SET RESOURCE_MANAGER_PLAN = 'IQCDB02';

 また、検証で使用したCPUに負荷をかけるサンプルコードを以下に示す。

 今回の検証用マシンは2CPU(16コア)のマシンを使っているが、いくつかのパターンで検証を行う。また、データベースにCPU負荷をかけるコードは以下を使っている。

declare
 t number;
begin
 for i in 1 .. 50000000 loop
  for j in 1 .. 100 loop
   t := t + 1;
  end loop;
 end loop;
end;
/

このPL/SQLコードを16セッションで同時実行する


パターン1

 CDBリソースプランを使用せずに、同一コンテナ・データベース内の複数のプラガブル・データベースに負荷をかける








 OSとしても、データベースとしてもリソース制限をかけていない状態だと、2つのプラガブル・データベースのワークロードはOSのプロセススケジューラーにより自動でスケジュールされ、ほぼプラガブル・データベース毎にほぼ同じだけのリソースを使うので、全体のCPU使用率は100%となり、処理時間も同じような結果となる。

パターン2


 CDBリソースプランを使用して、以下の相対的なCPU使用量を設定して負荷をかける



 これは、CPUリソースが枯渇した場合、CDBリソースプラン中のsharesで指定された総数(7+1=8)の内、各プラガブル・データベースにCDBリソースプランで指定した分だけ、CPUリソースを割り振る。この場合、PDB2には、CPUを7/8=87.5%割り振ることを意味している。



 パターン1で、均等のCPUリソースを割り振られたとするとPDB2には50%のCPUリソースが割り振られたことになる。パターン2では、87.5%が割り振られることになるので、処理は約1.75倍パフォーマンスアップしている計算になる。実際、パターン1で2分7秒だった処理が、1分20秒となり、パフォーマンスアップは約1.59倍。パターン1でのCPUリソースの割り振りが厳密に均等でないことを考えると、リソース管理としては十分合格点だと言える。

 さらに、パターン2のPDB3では、1/8しかCPUリソースが割り振られないにも関わらず、パターン1とほぼ同じパフォーマンスとなっている。これは、sharesパラメーターがCPUリソース枯渇時のみ有効であるため、PDB2の処理が終了し、CPUリソースに空きができたため、PDB3はその空きリソースを使用して処理している。これは、パターン2実施時のCPU使用率のチャートを見てもPDB2およびPDB3の処理が終了するまで、CPU資料率が100%であったことからも分かる。

パターン3


 CDBリソースプランを使用して、以下の絶対的なCPU使用量を設定して負荷をかける



 これは、パターン2のテストに加えて、CPU使用率の上限を絶対値で指定している。つまり、utilization_limitを指定すると、CPUリソースに空きがあっても、指定された上限を超えないようリソース制限がかけられる。




 PDB2はパターン2の時と大きく処理時間の変化はないが、PDB3では、処理時間に大きな変化があることがわかる。これは、CPU使用率のチャートからわかるように、全体のCPUリソースに空きがあっても、utilization_limit(PDB3の場合は12.5%)を超えないように調整された結果だと分かる。

パターン4


 インスタンス・ケージングとCDBリソースプランを使用して、複数コンテナ・データベースでCPU使用量を定義しつつ、プラガブル・データベースごとにもCPU使用量を設定して負荷をかける



 これは、パターン2のテストに加えて、コンテナ・データベースに対してインスタンス・ケージングを行っている。つまり上記のIQCDB02というコンテナ・データベースは全16CPUコア中、8CPUコアの制限がかけられている。その制限の中で、sharesパラメータによりさらにプラガブル・データベースにCPUリソース制限がかけられる事になる。




 パターン4では、使用可能なCPUリソースをパターン2のテストを比較して半分としたことから、処理時間も概ね倍の時間となった。また、CPU使用率のチャートからも、CPU使用率が50%を超えないようにリソース制御されていることが分かる。

OS ネイティブなリソースマネージャーを使った場合


 初期化パラメーターPROCESSOR_GROUP_NAMEを使用することで、Database Resource ManagerなしにOSのネイティブな機能を利用して、インスタンス・ケージングを行うことが可能になる。

注意
Linuxにおけるcgroupsの説明は紙面の都合上割愛するが、データベース側の$ORACLE_HOME/rdbms/install/setup_processor_group.shが参考になるので、興味のある方は覗いてみると良いと思う。ちなみにこのスクリプトはLinuxとSolarisのOSネイティブなリソースマネージャーに対応しているようである。


 検証環境のcgroupsの設定は以下の通り。




 OSネイティブなリソースマネージャー(cgroups)によりコンテナ・データベース全体がCPUリソース50%を超えないように調整されている。そのため、Database Resource Managerを使用したパターン1の場合と比較して、約2倍の処理時間となっていることが分かる。

 ただし、cgroupsがプロセス(スレッド)単位でOSがリソース制限をすることと、マルチテナント・アーキテクチャにおいて、プラガブル・データベース毎に、プロセスを生成するわけではないことを考えると、プラガブル・データベース毎にPROCESSOR_GROUP_NAMEでCPUリソースを制御することは不可能と言える。当然ながら、プラガブル・データベースでは、PROCESSOR_GROUP_NAMEパラメーターは変更できない。

OS ネイティブなリソースマネージャとCDBリソースプランを併用した場合


 今回、OSネイティブなリソースマネージャーとDatabase Resource Managerを併用した際、どのような動作になるのかも検証した。実際の運用において、どちらか一つに管理をまとめる方が運用効率が良いので、この併用プランを選択する必要性はないが、PROCESSOR_GROUP_NAMEをCPUリソースだけで使用しない場合(今後のI/Oで検証予定)を想定して、一応動作確認しておく。

検証環境のcgroupsの設定は以下の通り。






 OSネイティブなリソースマネージャー(cgroups)によりコンテナ・データベース全体がCPUリソース50%を超えないように調整されている。さらにCDBリソースプランにより各プラガブル・データベース間でのリソース管理が行われ、Database Resource Managerを使用したパターン4の場合と比較して、概ね同じ処理時間となっていることが分かる。

 本検証より、PROCESSOR_GROUP_NAMEとCDBリソースプランは併用しても期待通り動作していることが分かった。

CPUリソースを制御するといった観点でのまとめ


 本検証により、マルチテナント・アーキテクチャにおいて、CPUリソースの制御はDatabase Resource ManagerのCDBリソースプランによりかなり細かく制御が可能だと分かった。反面、Database Resource Managerでは、基本的にCPUリソースの制御しか提供されておらず、MemoryリソースやI/Oリソースの制御に関して疑問が残るのも事実である。

 今回、OSネイティブなリソースマネージャーを使用したのは、Database Resource Managerでの不足分を補うことが可能か否かを検証するためであるが、CPUリソース以外(特にI/Oリソース)の制御をOSネイティブなリソースマネージャーで実現できるかについて、今後の検証で明らかにしていきたいと思う。