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

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リソースの制御が可能であると思う。

コメント