【IRIS/Cache】グローバルざっくり解説#8 続・ループを早くする方法はある?

本記事は、グローバルのループ処理の高速化について引き続き解説します。

※この記事は下記の方向けになります。
  • ObjectScript初心者の方
  • データ・ブロックの容量について知りたい方
  • グローバルのループ処理について詳しく知りたい方

Q. ループを早くする方法はあるの?

A. 特殊な条件・環境であれば、早くする事は可能

同じ質問を「グローバルざっくり解説#6」で行いましたが、内容は異なります。

本記事は、データ・ブロックのサイズを変更する事で、ループ処理の速度が向上する事を検証したいと思います。

データベース作成時のウィザードで良く見る、この項目「ブロック・サイズ」を操作する形になります。

では、確認していきましょう。

ブロック・サイズとは?

データベース作成時に設定する項目で、一度データベースを作成してしまうと変更する事が出来ない項目になります。

ブロック・サイズを変更すると、1つのデータ・ブロックに格納されるグローバル数が増加します。

格納されるグローバル量が増加すれば、ディスクアクセスの回数がその分減ります。
アクセス回数が減れば、処理速度が向上する事になります。

風が吹けば桶屋が儲かる的な感じですね。

以上より、ブロック・サイズを増やす事のメリットになります。

とは言え、ブロック・サイズの基本が「8kb」である事には、理由があります。
以下は、ブロック・サイズを増加した時(ラージ・ブロック・サイズ)の注意点になります。

ラージ・ブロック・サイズのデメリット
  1. 8KBとは別のグローバル・バッファ用のメモリを用意する必用がある
  2. 8KBより多いメモリを設定する必用がある
  3. データ登録時や取得時に、ランダムに挿入や取得を行うと、パフォーマンスが下がる可能性がある。
  4. 少量のデータの場合、ブロック内の空き容量が大きくなる

そもそもシステム系のデータベース「IRISSYS」等は、8KBデータベースで構成されていたりするので、完全に8KBデータベースを無くす事はできません。

また、インデックス系のグローバル等は、8KBのデータベースで運用する方が良いです。

そのため、上記デメリットの問題が解消できない限り、使用は控えた方が良いと思います。

各サイズの選択肢を開放する方法

ブロック・サイズの変更は、初期インストールでは選択できないため、選択肢を開放する設定が必要です。

管理ポータルを起動し、[システム管理] > [構成] > [追加の設定] > [開始]と移動し、開始設定画面を起動します。

項目名「DBSizesAllowed」の「編集」をクリックし、開放したいラージ・ブロック・サイズにチェックを入れて、ボタン「保存」をクリックします。

今回は、64kBサイズを開放しました。

ウィザードのブロックサイズ選択で、64KBの選択肢が表示すれば成功です。

データベース・キャッシュ(グローバル・バッファ)の設定

ラージ・ブロック・サイズのデータベースを運用する場合、グローバル・バッファの値を設定する必用があります。

管理ポータルより、[システム管理] > [構成] > [システム構成] > [メモリと開始設定]と移動し、メモリと開始設定画面を起動します。

グローバルにアクセスすると、ブロックの全グローバルをメモリにキャッシュします。

ラージ・ブロック・サイズのデータベースを利用する場合、1回のグローバルアクセスでキャッシュするデータ量が増える為、少なくとも8KBの設定より多くのメモリを設定する必用があります。

速度検証

諸々の準備が整ったので、ループ速度の検証を行いたいと思います。

今回の検証では、データベースの構成を下記にしました。

検証用のデータベース構成手順
  1. インデックス・グローバル(Iグローバル)は、DB「TESTIDX」に格納し共有
  2. データ・グローバル(Dグローバル)は、DB「TEST8」に格納
  3. ②で作成したデータを、DB「TEST64」へGBLOCKCOPYコマンドを使用し、グローバルをコピー
  4. 検証時は、DB「TEST8」「TEST64」のグローバルマッピングを切り替えて行う

下記イメージに沿って作成します。

100万件のデータを作成しました。
データベースのブロック構成は下記になります。

DB名種類ブロック数データ量PackingContig.
TESTIDXインデックス7,22744,678,47676%7,226
TEST8データ(8KB)23,808172,693,94889%23,778
TEST64データ(64KB)2,924172,130,35690%2.923

64KBのデータ・ブロック数は、1/8まで少なくなりました。
1ブロックが8倍の容量に増えたので、計算通りですね。

インデックスは「患者ID」「漢字氏名」「カナ氏名」「生年月日」「血液型」を用意しました。

Index unique On patientId [ PrimaryKey ];

Index idxNm On 漢字氏名;

Index idxKana On カナ氏名;

Index idxBirth On 生年月日;

Index idxBlood On (ABO血液型, RH血液型);

諸々の準備ができたので、検証を開始しましょう。

Dグローバルをループさせる

データ・グローバル(Dグローバル)を単純にループする処理を作成し、両データベースの処理速度を比較します。

【検証PG】

ClassMethod LoopTest()
{
	s rowId=""
	f { s rowId = $o(^developer.data.Defrag1D(rowId), 1, data) q:rowId=""
		s $lg(,,,,,,,,,,,ptnId,name,kanaNm,,,,sex,,,,,abo,rh,) = data
	}
}

【検証結果】

項目 1回2回3回4回5回6回7回8回9回10回平均
8KB7.717.086.946.986.806.586.886.767.046.826.96
64KB3.002.712.912.542.922.623.092.732.493.032.80

単純に処理速度も8倍になる・・・わけではありませんが、3倍近くの処理速度は出ているのが確認できます。

SQLでの処理速度を比較する

Dグローバルのループ速度を比較したので、次はSQLの処理速度を比較します。

検証項目は下記3つで行います。

検証項目
  1. like」を使用した含む検索 → 漢字氏名、カナ氏名検索
  2. between」を使用した期間検索 → 生年月日検索
  3. 複合インデックスでの検索 → 血液型検索

各項目のクエリは、特に複雑に組んではいませんが、where節ではよく使用される検索方法と考えています。

では、検証を開始しましょう。

① 名称を検索する

名称を検索します。
検索結果はレコード全体の41%がHITする内容になります。

【検証PG】

ClassMethod SQLTest(name As %String, kana As %String)
{
	s start = $zh
	s cnt=0
	&sql(
		declare C200 cursor for
		select patientId,漢字氏名,カナ氏名,性別,ABO血液型,RH血液型
		into:ptnId,name,kanaNm,sex,abo,rh
		from developer_data.Defrag1
		where 漢字氏名 like :name or カナ氏名 like :kana
	)
	&sql(open C200)
	for {
        &sql(fetch C200)
        if (SQLCODE=100) { quit }
        s cnt = cnt + 1
	}
    &sql(close C200)
	
	
	s time = $zh-start
	s $li(^ZzTime, *+1)=time
	s ^ZzRowCnt=cnt
}

【検証結果】

項目1回2回3回4回5回6回7回8回9回10回平均
8KB10.810.412.212.812.213.013.712.513.812.512.4
64KB7.86.76.67.26.56.86.86.06.87.06.8

同じ条件ですが、64KBのデータベースは2倍の速度で処理が完了している事が確認できます。

② 生年月日を範囲検索する

生年月日を範囲検索します。
検索結果はレコード全体の89%がHITする内容になります。

【検証PG】

ClassMethod SQLTest2(from As %Date, to As %Date)
{
	s start = $zh
	s cnt=0
	&sql(
		declare C200 cursor for
		select patientId,漢字氏名,カナ氏名,性別,ABO血液型,RH血液型
		into:ptnId,name,kanaNm,sex,abo,rh
		from developer_data.Defrag1
		where 生年月日 between :from and :to
	)
	&sql(open C200)
	for {
        &sql(fetch C200)
        if (SQLCODE=100) { quit }
        s cnt = cnt + 1
	}
    &sql(close C200)
	
	
	s time = $zh-start
	s $li(^ZzTime, *+1)=time
	s ^ZzRowCnt=cnt
}

【検証結果】

項目1回2回3回4回5回6回7回8回9回10回平均
8KB13.915.115.915.216.915.315.017.315.59.915.0
64KB8.17.47.89.38.08.57.37.78.16.87.9

こちらも結果はあまり変わらず、64KBの方が2倍処理が早くなっています。

③ 血液型を検索する

血液型を検索します。
複合インデックスになります。

検索結果はレコード全体の12%がHITする内容になります。

【検証PG】

ClassMethod SQLTest3(abo As %Date, rh As %Date)
{
	s start = $zh
	s cnt=0
	&sql(
		declare C200 cursor for
		select patientId,漢字氏名,カナ氏名,性別,ABO血液型,RH血液型
		into:ptnId,name,kanaNm,sex,abo,rh
		from developer_data.Defrag1
		where ABO血液型 = :abo & RH血液型 = :rh
	)
	&sql(open C200)
	for {
        &sql(fetch C200)
        if (SQLCODE=100) { quit }
        s cnt = cnt + 1
	}
    &sql(close C200)
	
	
	s time = $zh-start
	s $li(^ZzTime, *+1)=time
	s ^ZzRowCnt=cnt
}

【検証結果】

項目1回2回3回4回5回6回7回8回9回10回平均
8KB9.810.07.88.27.48.48.07.88.07.08.2
64KB2.93.32.83.33.12.92.73.13.52.63.0

やはり64KBの方が早く、2倍以上の速度で処理が完了しています。

まとめ

Dグローバルを単純にループする場合もSQLでの検索でも、処理速度に差がでる事が確認できました。

全てのグローバルで同様の結果がでるとは限りませんが、グローバルの構成と使用用途を限定すれば、高い効果が見込める設定ではないかと思います。

おわりに

これまでの結果をまとめると下記になります。

まとめ
  • 64KBデータ・ブロックの方が処理が早い事がある
  • インデックスグローバルとデータグローバルは別のDBに格納する
  • データ量が少ないグローバルを格納するのは避ける
  • ラージ・ブロック・サイズ用のキャッシュメモリは多めに設定する
  • ランダムアクセスは避ける

大量データの検索を行うテーブルに対しては、かなり有効な設定ではないかと思います。
また、ドキュメントには、ストリーム・データにも向いていると記載があります。

新しくシステムを構築する際、これらの設定も考慮に入れてみてはいかがでしょうか。

以上、データ・ブロックの設定によるループ処理の処理速度向上について解説しました。
本記事が、皆さまの実務や学びの中で、少し