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を使用する
(*2) Linuxのcgroupsと初期化パラメーターPROCESSOR_GROUP_NAMEを使用する
I/Oリソース制御としてPROCESSOR_GROUP_NAME
注意
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リソースを制御するといった観点でのまとめ
前回のMemoryリソース編でも述べたが、マルチテナント・アーキテクチャでは、ワークロード種別やサービスレベル別で複数のコンテナ・データベースを作成し、そのコンテナ・データベース毎にプラガブル・データベースを作成し、データベースの集約を図るのが、筆者の思うベストプラクティスである。
今回のLinux cgroupsと初期パラメーターPROCESSOR_GROUP_NAMEを使用する場合、プラガブル・データベース単位でのI/Oリソースの制御はできない。しかし、DWH用コンテナ・データベースには高スループット、開発用コンテナ・データベースには低スループット等の設定が可能となる。これにより、完璧ではないが、最低限のI/Oリソースの制御が可能であると思う。


コメント
コメントを投稿