【IRIS/Cache】グローバルざっくり解説#5 ブロックの断片化って悪影響あるの?

本記事は、ブロックの断片化について解説します。

※この記事は下記の方向けになります。
  • ObjectScript初心者の方
  • グローバル構造について詳しく知りたい方
  • ブロックの断片化について知りたい方

Q. ブロックの断片化って悪影響あるの?

A. 一部ループ処理で、処理速度が低下します。

ストレージの断片化しかり、ブロックの断片化しかり…「断片化」という字面のイメージから、「処理速度に影響がない」なんて、思う方は居ないでしょう。

先ずは、断片化の起こる原因を数点確認しつつ、実際に速度を検証してみたいと思います。

断片化について

そもそも、ブロックの断片化とはどのような状態を指すのでしょうか。

各ブロックには、番号が振られている事については、「グローバルざっくり解説#1」で解説しました。
同じグローバル名のデータ・ブロックが、連続して配置されていない事を「断片化」状態となります。

下記の例では、全61個のデータ・ブロックに対し、58個のデータ・ブロックが「連続」している事を示しています。

同じグローバル(インデックス)を再作成(%BuildIndices)したら、全てのデータブロックの「連続性が0=断片化」していました。

ただ、インデックスを再作成しただけなんですが・・・

インデックスの再作成を行うと断片化する訳ではありません。
データベースの状況等により、結果は変わってきます。

断片化が起こる状況

システムを長期運用すると、自然にデータベースは断片化していきます。

断片化が発生する原因として、知りうる範囲では下記要因が挙げられます。

ブロックが断片化する要因
  1. 初回のブロックが分割する時
  2. 隣のブロックが埋まっていた時
  3. グローバル追加・値の増加によってデータ・ブロックが分割した時

他にも断片化が発生する要因をご存じの方がいたら、ひっそり教えていただけると幸いです。

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

ブロックの充填率

充填率(Packing)は、データ・ブロックに格納されるデータ量の割合(%)になります。

1つ目のブロックにグローバルを格納する

下記グローバルを実行し、充填率の変化を確認してみます。

f pos=1:1:1015  s ^Packing(pos)=""

この状態で、グローバルの容量を確認すると、充填率が脅威の「100%」と表示されます。

【データブロックの状態】
データブロックは1つで、容量が100%の状態

1つ目のブロックを溢れさせる

後は、徐々にグローバルを足していきましょう。

s ^Packing(1016)=""
s ^Packing(1017)=""
s ^Packing(1018)=""
s ^Packing(1019)=""
s ^Packing(1020)=""

グローバルの容量を確認すると、ノードが1020の時に溢れました。

100%まで増加したのち、2つに分かれたので充填率「50%」に下がりました。
① 初回のブロックが分割する時」は、ポインタ・ブロックも生成されるので、だいたい連続する事はないようです。

では、各ブロックの構成を確認してみます。
 # 文字が灰色の行は、近隣の無関係なブロック情報です。

ブロック番号ブロックタイプ開始グローバルノードレコード数割合
29892下部ポインタ・ブロック^developer.data.PatientS(略)
29893データ・ブロック^Packing91790%
29894データ・ブロック^BlockSearch
===============================
34923データ・ブロック^ISCMethodWhitelist
34924ポインタ・ブロック
34925データ・ブロック^Packing(917)10410%
34926データ・ブロック^ROUTINE

最初のデータ・ブロックだけ、大きく外れたブロック番号になっています。
また前後のブロックは、全くの無関係なデータ・ブロックなので、たまたま「# 29893」が空いていたのでしょう。

空いていた隙間に格納された事により、次のデータブロックが「# 34924」に格納されました。

また、「#34925」の隣も既に埋まっているため、次のデータブロックは断片化する事が確定しています。
② 隣のブロックが埋まっていた時」を確認しました。

データブロックの状態
データブロックが2つになり、最初のデータブロックは充填率が90%に減少。
減った分は、2つ目のブロックに格納される。

2つ目のブロックに追加のデータを詰める

さて、このままノードを1935まで増加させます。

f pos=1021:1:1935  s ^Packing(pos)=""

グローバル全体の充填率は95%になりました。

ブロックの状況です。
最初のデータ・ブロックは変動がなく、2番目のブロックに新規グローバルが格納されています。

ブロック番号ブロックタイプグローバルノードレコード数
29893データ・ブロック^Packing ~ ^Packing(916)917
34925データ・ブロック^Packing(917) ~ ^Packing(1935)1,019

データブロックの状態
2つ目のデータブロックの充填率が100%になった。
1つ目のデータブロックは特に変化なし。

この先、さらにデータを追加していくと、2番目のデータ・ブロックの充填率は90%程度に下がり、3番目のデータ・ブロックが作成されると思われます。

そしてさらにデータを増加していくと、それが次から次へと繰り返されていく事になります。

1つ目のブロック(# 29893)に追加のデータを詰める

では、この状態で、1番目のデータ・ブロックにデータを追加したらどうなるでしょうか。

第2ノードを追加し、1番目のデータ・ブロックのデータ容量を増やします。
折角10%の空き容量があるのですが、実験なので仕方なしです(笑

f pos=1:1:110  s ^Packing(500, pos)=""

1番目のデータ・ブロックが分割されました。

ブロックの状況を確認します。

ブロック番号ブロックタイプグローバルノードレコード数
29893データ・ブロック^Packing ~ ^Packing(660)771
469688データ・ブロック^Packing(661) ~ ^Packing(916)256
34925データ・ブロック^Packing(917) ~ ^Packing(1935)1,019

新規に分裂したデータ・ブロックは、かなり離れた位置に作成されました。
これが「③ グローバル追加・値の増加によってデータ・ブロックが分割した時」になります。

断片化が激しいですね。

データブロックの状態
1つ目のデータブロックが、データ量の増加によって分割。
2つ目のデータブロックは変化なし。

以上、ブロックが断片化する要因を色々見てきました。

次は、断片化した状態と、連続した状態での処理速度を確認します。

処理速度を比較する

意図的に断片化を引き起こして、断片化したデータベースと、断片化していないデータベースで処理速度を比較してみたいと思います。

準備

データ・クラスを5個用意し、Populateで5個同時に100万件のデータを生成します。

同時にデータを作成する事で、意図的に断片化を発生させます。
 ※Populateの実行は、5個のターミナルを使用するか、jobコマンドで行って下さい。

データ作成後、データベース(DATファイル)をコピーして、片方にデフラグを実行します。

これで同じデータを持った「断片化したデータベース」と「非断片化したデータベース」が用意できました。

参考)作成したグローバルの状態

グローバル名ブロック数断片化状態
Packing
断片化状態
Contig.
デフラグ後
Pcking
デフラグ後
Contig.
developer.data.Defrag1D23,80789%5189%23,806
developer.data.Defrag1I16,79276%10,83776%16,791
 ※ デフラグ方法に関しては、後程記載します。

グローバルを単純にループさせる

このグローバルのノード(レコードID)を全件ループして、処理速度の差を検証したいと思います。

ClassMethod defLoop()
{
	s start = $zh
	s rowId = ""
	f { s rowId = $o(^developer.data.Defrag1D(rowId),1,data) q:rowId=""
	}
	w !,$zh - start
}

【検証結果】

項目 1回2回3回4回5回6回7回8回9回10回平均
断片化8.257.877.718.338.817.817.948.968.108.408.22
デフラグ後8.017.437.217.296.887.066.146.097.567.157.08

100万件をループするだけで、1秒の差が生まれました。

SQLを実行し、レコードの検索を行う

次はSQLで、全レコードの30%のデータを検索してみたいと思います。

ClassMethod sql(name As %String)
{
	s start = $zh
	&sql(
		declare C200 cursor for
		select 性別,生年月日,ABO血液型 into:性別,:生年月日,:ABO血液型
		from developer_data.Defrag1
		where 漢字氏名 like :name or カナ氏名 like :name
	)
	&sql(open C200)
	f {
        &sql(fetch C200)
        if (SQLCODE=100) { quit }
	}
    &sql(close C200)
	w !,$zh-start
}

【検証結果】

項目 1回2回3回4回5回6回7回8回9回10回平均
断片化21.921.319.820.720.019.822.421.422.620.521.0
デフラグ後16.717.719.418.114.916.717.317.117.715.017.1

レコード全体の30%程を検索するだけで、4秒の差が生まれています。

ディスクからデータを読み取るとき、どうしてもシークが発生します。
これは、構造上仕方のない動作ですが、データブロックが断片化していると、多少の影響が出ているようです。
 ※ 検証端末=ハード・ディスク

デフラグを解消する

デフラグの解消については、下記を参照してください。

管理ポータルで、お手軽にデフラグする方法を少し触れたいと思います。

[システムオペレーション] > [データベース] で「データベース]画面を起動します。

デフラグを行うデータベース名をクリックすると、「データベースの詳細」画面が起動します。

後は「デフラグ」ボタンをクリックするだけです。
楽ちんです。

デフラグ実行時に、データベース(DATファイル)が肥大化する事があります。
ストレージの容量確保をご確認下さい。

肥大化したデータベースは、「圧縮」で小さくする事が可能です。

まとめ

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

まとめ
  • ブロックは、長期運用を行うと自然に断片化する
  • 断片化すると、一部の処理に悪影響が発生してしまう
  • デフラグを行うと解消する・・・が、運用中は中々難しいですよね

定期的なメンテナンスを行うことで、初期の処理速度を維持する事が可能です。

以上、ブロックの断片化について解説しました。
本記事が、皆さまの実務や学びの中で、少しでもお役に立てれば幸いで