本文へジャンプ

データベース/データ管理  >  DB2 Developer Domain  >  ライブラリー  >  技術白書-DB2ファミリー関連  >  
   
 

Informix DSS照会のチューニング

Jack Parker
Arten Technology Group
2002年5月

 
 
コンテンツ
はじめに
監視ツール

照会の例

基本的なチューニングの手順
データの分散
PDQPRIORITYにより並列を使用可能に
バッファー・オーバーヘッドを解消するためのライトスキャンの強制
索引の追加の検討

一時ディスク

バランス

モデル変更

まとめ

執筆者について

 執筆者
Jack Parker

Arten Technology Group

 

この記事は当初Informix Tech Notes Volume 10、2000年第1号に掲載されたものです。IBM Informix® Dynamic ServerTM(IDS)向けに書かれました。内容はIBM Informix Foundationにもあてはまります。IBM Informix Extended Parallel Server(XPS)向けに一部コメントを追加してあります。

はじめに
過去数カ月にわたり、私はIBM Informix database用意思決定支援システム(DSS)のチューニングの概念を繰り返し説いてきました。

データウェアハウスと非常に大規模なデータベースは、従来のWRITE OUT(OLTP)データベース・システムとは異なる使い方をされる傾向があります。OLTP環境では、目標は短い時間フレームで数多くの別個のトランザクションを実行することです。OLTP管理者は、数百、数千というユーザーをサポートし、データの非常に具体的な部分に注目しがちです(たとえば、顧客の注文の項目4およびその注文マスター・レコードの残高を更新できなければならない)。そのようなOLTPトランザクションは、1つまたは複数のUPDATEステートメントWHERE cust_id/ord_no = ?で実行できることもあります。

DSS環境において、ほんの一握りのユーザーしか抱えておらず、その各々が大量のデータに対して照会を行っている、という可能性はさらに高くなります。OLTPモデルでは、このようなユーザーはひどい罰を受けることになります。しかし、DSSの世界では、この種のアクティビティーは奨励されます。通常、DSS照会は1つまたは複数の表のあらゆる行をスキャンします。さらに、通常、OLTPシステムのサイズが百メガバイト単位で測定される状況で、DSSシステムは百ギガバイト単位で測定されます。

DSSシステムは、Business ObjectsまたはSASのようなフロントエンド・ツールによって照会が行われる傾向があり、照会を行うSQLはツールによって生成されます。往々にして、照会そのものをあまり管理できないことが多くなります。

OLTPシステムで使用される同じデータ・モデル、索引読み取り、エンジンのチューニングを用いてDSS照会をサポートしようとする場合、そのような照会の応答時間は往々にして日にち単位で測定されることになりなります。ごく最近の事例では、そのような照会の応答時間が40日でした。このような特殊な状況で私は、この記事で詳しく説明しているチューニング方式を使用して、照会結果時間を約15分まで短縮することができました。これは、照会に変更を加えることも、スキーマを変更することもなく実現できたのです。この記事では、DSS照会の実行時間を短縮するチューニング手順について具体的に説明します。

上に戻る

監視ツール
このDSS照会を最適化するためのチューニング手順を検討する前に、利用できる具体的な監視ツールの使用方法を理解しておく必要があります。こうしたツールは照会がどのような振る舞いをするか把握する上で役立つものです。以下の監視ツールは、照会を検討する際に非常に便利なものです。

  • Explain plan
  • Xtree
  • onstat -g mgm command
  • onstat -g lsc command
  • onstat -g xqpおよびxqs(XPS-固有)
  • xmp_mon.

Explain plan
最初の監視ツールは、explain planです。これは、照会の先頭にSET EXPLAIN ON;コマンドを記述することによって利用できます。最適化プログラムがsqexplain.outという名前のファイルを作成するか、または追加します。このファイルでは、リスト1に示すように、照会を解決するプランをどのように立てるか説明されています。

リスト1. Explain planの出力

QUERY:
------
select a.state_cd, b.last_name, count(*)
from tab1 a, tab2 b
where a.key = b.key
group by 1,2
Estimated Cost: 121157
Estimated # of Rows Returned: 1
1) informix.b: SEQUENTIAL SCAN (Serial, fragments: ALL)
2) informix.a: SEQUENTIAL SCAN (Serial, fragments: ALL)
Temporary table required for group operation
DYNAMIC HASH JOIN (Build Outer)
Dynamic Hash Filters: informix.b.hh_id = informix.a.hh_id

見積りコストが非常に低いことに注目してください。UPDATE STATISTICSを実行し忘れたようです。

このEXPLAIN planの最初のセクションは、照会に続いて見積りコストが表示されています。これは、照会を解決するために必要であると最適化プログラムが判断する命令の数から導かれます。ここで示されている値は、この照会に対してはあまりに小さすぎます(10億単位の数値であればより標準的といえます)。

返される行の予想数は、少なくとも1つの表が空であると最適化プログラムが見なしている示度です。最低1つの行が常にcount(*)オペレーションから返されます。

おそらくは空の表の読み取りにはコストが生じないために、各々の表は順次スキャンを使用して読み取られます。「Serial」は、表のあるディスクの各フラグメントが、望ましい「並列」ではなく、順次に読み取られることを示しています。フラグメントは照会から除去されていません(それゆえ「fragments: ALL」です)。

一時表は、もちろん、実際のgroup byオペレーションを実行するために必要です。2つの表を結合するために、ハッシュ結合が使用されます。この場合も、表の1つが空であるため、またこれらの表に索引がないからです。

実際に表示させたいのは、もっと高い見積りコストです。望ましいのは順次スキャンです。本当は逐次ではなく、並列スキャンなのですが。WHERE条件(フィルター)を通じてフラグメントの一部を除去できたのであれば、スキャンすべきフラグメントのリストだけを表示したかもしれません。最終的に、次のような行も表示させたいと考えました。

Maximum Threads: 21

実際には、より高い並列性を示すもっと大きい値を表示させたかったのですが、この例の性質上、21にします。これは、PDQPRIORITYを使用していることを示しています。スレッドの正確な数は、各表がまたがっているDB領域の数と、各追加タスクに割り当てられているスレッドの数によって異なります。リスト2に示される onstat -g sqlコマンドの出力でご覧いただけるように、1つの表に9つのスキャン・スレッドがあり、2番目の表に11のスキャン・スレッドがあり、すべてをまとめる1つのsqlexecスレッドがあります。PDQPRIORITYをさらに高く設定すると、グループ化とソートのさらに特殊化されたスレッドを表示することができます。

これらの情報の主要な断片から、照会を解決するために選ばれた最適化プログラムに何か問題があることが分かります。

Xtree
explain planは照会がどのように回答されるか示すことができますが、Xtreeは照会が実行されている間にこれを監視できるようにします。プロセスのセグメントごとにいくつのスレッドが開始されているか知ることも、また照会の実行速度も判断することができます。

以下の図1は、代表的なハッシュ結合のXtree画面ショットを示しています。この図の下部には、2組のスキャン・スレッドがあります。右側は9スレッド、左側は11スレッドで実行しています。右側の表は、ハッシュ・テーブルのメモリーに読み込まれ、左側の表は読み込み中にこのハッシュに対してスキャンと突き合わせが行われています。

各ボックスの右の数字は、照会のこの部分を実行するために使われているスレッドの数を示しています。ボックス内のメーター表示の下には、この1秒間に照会のこの部分によって処理された行数を示す数字があります。ボックスの上部には、これまで処理された行数が示されています。約114,000行がこの1秒間に左側の表から読み取られ、同じこの1秒間に約89,000の突き合わせが行われました。ハッシュ・テーブルのスワップが発生すると、毎秒プロセス・カウンターがすべてゼロに落ちます。こうした状況を監視することで、プロセスがどのくらいの頻度でどのくらいの時間ハッシュ・テーブルのスワップに費やしているのか突き止めることができます。

スキャン・ボックスの横にある数字は、表が断片化されるDB領域の数値と一致するはずです。このマシンでは、毎秒フラグメントあたりおよそ50,000〜70,000行程度がスキャンされると見込んでいます。この数値が最後に落ち込んだ場合は、1つまたは複数のフラグメントがおそらくは他のフラグメントよりも先に完了しています。この状態が最後の5〜10秒で発生した場合にはそれで大丈夫ですが、その落ち込みが照会の半ばで発生した場合には、データ・スキューの断片化スキームを調べたほうがよいでしょう。

フィルター・ボックスは、フィルターを使用している場合には(where state_cd = 'MA'など)、スキャンされた行数を減らすはずです。フィルターされた合計数は、スキャン速度とすぐに同じペースになるはずです。

図1: Xtreeハッシュ結合

<クリックして拡大>

図2は、典型的なネストされたループ結合を示しています。左側の表は、順次スキャンされています。次に、2番目の表へのプローブがこの最初の表から行ごとに行われます。ご覧のように、このスナップショットがとられた瞬間に、照会が1,373行のすべてを結合しましたが、これはあまり感心しません。ここでは、結合自体がスキャン速度を制限しています。さらに興味深いことに、読み取られた行よりも多くの行が結合されていることがわかります。これは重複する行があることを示していますが、同じ住所に複数の人が住んでいることと同じことになるので容認できます。

図2: Xtreeのネストされたループ結合

<クリックして拡大>

onstat -g sesおよびonstat -g sqlコマンド
コマンドonstat -g sesおよびonstat -g sql(リスト2)は、一部の重要な数値を明らかにします。具体的には、照会を実行するために使用されるスレッドの数とタイプ、および照会に割り振られているメモリーの量です。

リスト2. onstat -g sesおよびonstat -g sqlコマンドの出力
session                                   #RSAM    total      used       
id       user     tty      pid   hostname threads  memory     memory
320      informix 6        25979 foo      21       352256     278408     

tid name rstcb flags curstk status 48671 sqlexec e041518 ---P--- 1984 e041518 ready

Controlling sqlexec thread


48672 scan_1.0 e03fb98 ------- 984 e03fb98 sleeping(secs: 3) 48673 scan_1.1 e040c98 ------- 984 e040c98 running 48674 scan_1.2 e045918 ------- 984 e045918 running 48675 scan_1.3 e046e58 ------- 984 e046e58 sleeping(secs: 3) 48676 scan_1.4 e0443d8 ------- 984 e0443d8 ready 48677 scan_1.5 e044818 ----R-- 984 e044818 running 48678 scan_1.6 e044c58 ------- 984 e044c58 ready 48679 scan_1.7 e045098 Y------ 936 e045098 cond wait(await_MC1) 48680 scan_1.8 e048398 Y------ 936 e048398 cond wait(await_MC1)

9 Scan threads (0-8) for Table 1


48681 scan_2.0 e042618 Y------ 936 e042618 cond wait(await_MC2) 48682 scan_2.1 e043718 Y------ 936 e043718 cond wait(await_MC2) 48683 scan_2.2 e0410d8 Y------ 936 e0410d8 cond wait(await_MC2) 48684 scan_2.3 e0421d8 Y------ 936 e0421d8 cond wait(await_MC2) 48685 scan_2.4 e046a18 Y------ 936 e046a18 cond wait(await_MC2) 48686 scan_2.5 e041d98 Y------ 936 e041d98 cond wait(await_MC2) 48687 scan_2.6 e047298 Y------ 936 e047298 cond wait(await_MC2) 48688 scan_2.7 e045d58 Y------ 936 e045d58 cond wait(await_MC2) 48689 scan_2.8 e0454d8 Y------ 936 e0454d8 cond wait(await_MC2) 48690 scan_2.9 e03eed8 Y------ 936 e03eed8 cond wait(await_MC2) 48691 scan_2.1 e0487d8 Y------ 936 e0487d8 cond wait(await_MC2)

11 Scan threads (0-10) for Table 2


Memory pools count 1 name class addr totalsize freesize #allocfrag #freefrag 320 V e2de018 352256 73848 1420 14
name free used name free used overhead 0 120 scb 0 96
opentable 0 31208 filetable 0 8384 log 0 44856 temprec 0 17680 ralloc 0 38560 gentcb 0 18728 ostcb 0 2024 sqscb 0 7664 rdahead 0 1600 xchg_desc 0 7392 xchg_port 0 2312 xchg_packet 0 5280 xchg_group 0 144 xchg_priv 0 1352 hashfiletab 0 5880 osenv 0 1672 buft_buffer 0 19224 sqtcb 0 24192 fragman 0 36720 shmblklist 0 3320

Sess SQL Current Iso Lock SQL ISAM F.E. Id Stmt type Database Lvl Mode ERR ERR Vers
320 SELECT hercules NL Not Wait 0 0 7.24
Current statement name : slctcur
select a.state_cd, b.last_name, count(*)
from tab1 a, tab2 b
where a.key = b.key


Current SQL Statement


Last parsed SQL statement : select a.state_cd, b.last_name, count(*) from tab1 a, tab2 b
where a.key = b.key

出力には、多くの情報が含まれています。もしかすると必要以上かもしれませんが、着目すべき重要な手がかりがいくつかあります。現在のステートメント名は、セッションが実際に実行している照会のSQLに関する詳細を提供します。開始されたスレッドの数とタイプが分かります(たとえば、scan_2.3など、1つの表に対して9のスキャン・スレッド、他の表に対して11のスキャン・スレッド、PDQPRIORITY=1(照会を開始したのは私なので、これは分かります)でハッシュ結合を行う1つのsqlexecスレッドがあります。onstat -g mgmを使ってPDQPRIORITYを表示します)。

onstat -g mgmコマンド
onstat -g mgm(XPSではrgm)コマンドもメモリー使用量を示してくれますが、これは複数の照会にとって特に重要です。どの照会がリソースを待っているのか示してくれるからです。Memory(Resource)Grant Managerは、どの照会が実際に実行中であるか、どの実際のPDQ設定で実行しているか、さらにどの照会が「ゲートされている」か、または実行するためにリソース(たとえばメモリーまたはPDQ)を待っているかを示します。リスト3を参照してください。

Memory Grant Manager (MGM)
MAX_PDQPRIORITY: 100 
DS_MAX_QUERIES:  20 
DS_MAX_SCANS:  40 
DS_TOTAL_MEMORY: 20000 KB 
Queries: Active Ready Maximum 1 0 20
Memory: Total Free Quantum (KB) 20000 0 1000
Scans: Total Free Quantum 40 39 1
Load Control: (Memory) (Scans) (Priority) (Max Queries) Gate 1 Gate 2 Gate 3 Gate 4 (Queue Length) 0 0 0 0
Active Queries:
Session Query Priority Thread Memory Scans Gate  
33 105e4060 100 106335d70 2500/2500 2/1 -
Ready Queries: None

これは9.20エンジンから実際に出力されます。7.xおよび8.xメモリー/リソース・グラント・マネージャーの出力と同じ基本要素を含んでいます。この場合には、インスタンスは限られたメモリーでLinux上で実行します。ご覧のとおり、20MBのDSメモリーがあり、1MBのQuantumsで割り当てられています。40のスキャンが可能ですが、その1つが実行中です。問題となっている照会(一番下のセッション33)は、すべてのメモリーと、必要な2つのスキャンの1つを使っています。マルチユーザー環境で特に関心を引くのは、Gateです。2番目の照会がサブミットされると「Ready Queries」のもとにリストされます。MemoryおよびPriorityのもとの待ち行列の長さは1になります。照会はメモリーまたはPDQPRIORITYでなければ実行できないからです。

onstat -g lscコマンド
onstat -g lsc
(XPSではscn)コマンドは、ライトスキャンがあるかどうか示します。このコマンドからの出力がない場合には、ライトスキャンはありません。ライトスキャンは、以下に示すように、DSS照会にとって極めて重大になることが多くあります。

onstat -g xqpおよびxqsコマンド(XPS固有)
XPSでは、xtreeツールはありません。しかし、xtreeよりもさらに詳細な情報が提供されます。onstat -g rgm出力から、プラン番号がわかります。これをonstat -g xqp plan_noに入れると、リスト4に示すように、照会を解決するために使用されるプランが出力されます。

リスト4. onstat -g xqpコマンドの出力


XMP Query Plan oper segid brid width misc info ----------------------------------------- scan 4 0 16 tab1 group 4 0 16 group 3 0 16 group 3 0 16 group 2 0 16 insert 1 0 32

リスト4の出力は、以下の照会を参照しています。


insert into tab_2 select col1, count(unique col2) from tab1 group by 1 This is running on an XPS 8.30.UC2 with 16 processors and 8 GB of DS memory.

オペレーションは、segidに基づいて降順に行われます。ですから最初のオペレーションはscan/groupです。group自体は、入力に1回と出力に1回の2回レポートされます。この場合「unique」文節が第2のgroupを強制しました。最後に、データは最終表に挿入されます。

リスト5〜10は、plan_idのコマンドonstat -g xqsの部分的出力を示しています。

簡略にするため、各セグメントの最初の1行と最後の3行以外をすべて削除しました。

リスト5. コマンドonstat -g xqsの部分出力(第1部)

XMP Query Statistics Cosvr_ID: 1 Plan_ID: 11961 type segid brid information ---- ----- ---- ----------- scan 4 0 inst cosvr time rows_prod rows_scan ---- ----- ---- --------- --------- 0 1 1916 39433808 39433808 1 1 1919 39471825 39471825 2 1 1903 39422536 39422536 13 4 1930 39410387 39410387 14 4 1936 39364283 39364283 15 4 1936 39375704 39375704 ------------------------------------------- 16 630463773 630463773

このセグメントでは、16のスレッドが開始されました。各スレッドは、約32分で3940万行を読み取りました。

(timeの列は秒単位で表示されています。)たいしたことはありません。フィルターがなかったので、生成された行の数(rows_prod)はスキャンされた行の数(rows_scan)と等しくなっています。

リスト6. コマンドonstat -g xqsの部分出力(第2部)

group 4 inst cosvr time rows_prod rows_cons mem ovfl ---- ----- ---- --------- --------- --- ---- 0 1 1916 39433808 39433808 104 0 1 1 1919 39471825 39471825 104 0 2 1 1903 39422536 39422536 104 0 13 4 1930 39410387 39410387 104 0 14 4 1936 39364283 39364283 104 0 15 4 1936 39375704 39375704 104 0 ----------------------------------------------------------------- 16 630463770 630463773 (2048)

各「group」オペレーションは、データの収集と実際のgroupという2つのフェーズに分割されます。したがって、リスト6で示されるセグメントは、最初のgroup(固有)オペレーションに行を押し込んだことを表しています。この場合も、16スレッドで同じ時間がかかり、これはスキャン時間と同時でした。生成された行数は、押し込まれた行数(rows_cons)と同じです。このオペレーションのメモリー量は2Kでした(16スレッド全体)。

リスト7. コマンドonstat -g xqsの部分出力(第3部)

group 3 inst cosvr time rows_prod rows_cons mem ovfl ---- ----- ---- --------- --------- --- ---- 0 1 12639 39059408 39432594 49345600 199 1 2 12676 39071879 39442561 49328000 199 2 3 12203 39054649 39429975 49336000 199 13 2 12672 38998788 39393276 49350400 199 14 3 12103 39003511 39390257 49345600 199 15 4 11942 39011955 39381624 49329600 199 ----------------------------------------------------------------- 16 624401945 630463770 (3958528)

リスト7は、リスト6で示されているgroupセグメントの後半部分で、ここでは固有の行が現れています。このフェーズではほぼ4GBのメモリーを使用していることに注目してください。ovfl列は、メモリーがオーバーフローしたことを示しています。ovflは、オーバーフローしたページ数のカウントではなく、オーバーフローしたセグメント数のカウントです。リストからオーバーフローが重大なものであるかどうか見分けることは困難です。しかし、このセグメントに3時間かかったことを考慮すれば、これが重大であったことは推測できます。これこそ、おそらくはスキャンをゲート制御したものでしょう。

リスト8. コマンドonstat -g xqsの部分出力(第4部)

group 3 inst cosvr time rows_prod rows_cons mem ovfl ---- ----- ---- --------- --------- --- ---- 0 1 12639 39059408 39059408 104 0 1 2 12676 39071879 39071879 104 0 2 3 12203 39054649 39054649 104 0 13 2 12672 38998788 38998788 104 0 14 3 12103 39003511 39003511 104 0 15 4 11942 39011955 39011955 104 0 ----------------------------------------------------------------- 16 624401944 624401945 (2048)


リスト8には、count(*)/groupオペレーションによって必然的に伴う第2のgroupが含まれます。この場合も同様に、これはgroupオペレーションに行を押し込んでいるだけです。この時間はすべて以前のプロセスと同時です。

リスト9. コマンドonstat -g xqsの部分出力(第5部)

group 2 inst cosvr time rows_prod rows_cons mem ovfl ---- ----- ---- --------- --------- --- ---- 0 1 13203 21639904 39058139 43422720 199 1 2 13136 20410129 39069022 40452896 199 2 3 13201 22846215 39073721 46132152 199 13 2 13225 21678939 39022613 43424128 199 14 3 13180 21694541 39009538 43659528 199 15 4 13184 22908683 39014754 46380352 199 ----------------------------------------------------------------- 16 352937467 624401944 (3958528)

リスト9は、2番目のgroupオペレーションの第2フェーズを示しています。この照会が作成された方法に原因があって、このオペレーションに他の4GBのメモリーが使用されました。このパフォーマンスの問題を解決するための1つの戦略としては、2つのgroupを分割することがあります。1つの表にselect uniqueを行い、次にその2番目の表にcount offを行います。

リスト10. コマンドonstat -g xqsの部分出力(第6部)

group 2 insert 1 0 inst cosvr time it_count ---- ----- ---- --------- 0 1 2380 10979878 1 2 2380 10979878 2 3 2380 10979878 29 2 2380 10979878 30 3 2380 10979878 31 4 2380 10979878 ----------------------------- 32 351356096

リスト10は、一時表に書き出す照会の最終挿入フェーズを示しています。オペレーションのこのフェーズは、2番目のgroupが出力の発行を始めるまで開始されませんでした。従って時間がはるかに少なくなります。

xmp_monスクリプト
onstat -g xqsからの出力はさらに、explainプランに書き出されます。XPS 8.31では、xqs出力はスレッドが完了するごとに書き出されるだけです。リアルタイム出力の場合は、至れり尽せりのonstat -g xmpコマンドを使用する必要があります。私には、この種の情報を使いやすい形式にまとめる独自のスクリプトがあります。(このスクリプトは、 www.artentech.com/downloads/xmp_mon から利用できます。)xmp_monのサンプル出力は、リスト11に示しています。

リスト11. xmp_monの出力

===Sat Apr 27 10:48:57 EDT 2002=================================== User: informix Isolation: DR PDQ 50.00-50.00 Plan : 15 Actual Memory: 8000000 (50%) Scheduling level : 50 Current SQL Statement : insert into tab_2 select col1, count(unique col2) from tab1 group by 1 scan 4 tab1 group 4 Operation Phase Threads Rows scan next 16 304067921 group 3 group 3 Operation Phase Threads Rows xchg next 16 302983421 group create 16 0 group 2 insert 1

出力は、照会が表tab1をスキャニング中であることを示しています。3億行がスキャンされ、最初のgroupオペレーションに渡され、そこで待機しています。上部にあるのは照会、分離レベル、PDQおよびメモリーといったいくつかの主要要素です。

上に戻る

照会の例
2つの表を結合して、州別の姓のカウントを返す照会があると想定します。名前はTable 1にあり、住所(州)はTable 2にあります。


select a.state_cd, b.last_name, count(*) from tab1 a, tab2 b where a.key = b.key group by 1,2

Table 1には2,900万行あり、Table 2には2,700万行あります。Table 1は4.5 GBで、Table 2は6 GBです。表はそれぞれ、単一のDB領域に置かれています。いずれの表のいずれのキーにも索引はありません。使用しているマシンは、メモリー4GB搭載の8プロセッサーSun 6500です。この照会がOLTP環境で実行されたとすれば、実行にさらに70または80時間を要したことでしょう。

往々にして、DSS照会のチューニングは単に「OLTP-think」から「DSS-think」への移行の問題ですが、まさにそれはここで行うことなのです。

最適化プログラムは、ハッシュ結合で例の照会を解決することにするはずです。そうしなければ、ここでの照会の場合のように表の1つが空であると最適化プログラムが見なしてしまうような、別の問題が生じることもあります(ですから、UPDATE STATISTICSを実行する必要があります)。PDQPRIORITYに何も設定されていないと想定することにしましょう。これはOLTP環境に特有のものです。これはつまり、効果的にオフにされるということです。照会が実行されると、どのようなことが生じるでしょうか。

以上の設定で、エンジンは最初に小さいほうの表(Table 2)を読み取り、フィルターを適用し、各レコードにハッシュ・テーブル・エントリーを構築します。これは、このハッシュ・テーブルをメモリー内に構築します。次に大きい表を読み取り、これをハッシュ・テーブルに突き合わせます。一致が見つかると、一致した行を結合の反対側に押し出します。

2つの表のうち「大きいほうの」表のフィルターが、その表からの結果セットを、「小さいほうの」表からの結果セットよりも小さくすることは可能です。最適化プログラムがこのことを認識していれば、フィルターを適用して大きいほうの表を先に読み取るよう適切な選択をして、ハッシュ・テーブルが大量のメモリーを占めないようにします。最適化プログラムには、この判断をより正確に行うために分散情報が必要ですが、これはUPDATE STATISTICS highまたはmediumを実行することによってしか得られません。

この照会で37分後に、Xtreeは両方の表がスキャンされてハッシュ結合に移動したことを示します。割り振られているメモリーが十分ではないので、一時ディスクからハッシュ・テーブル・ページをスワップ中にハッシュ結合は停止します。ハッシュ・テーブルの構築にさらに多くのメモリーが必要になると、それに応じてさらに多くのメモリーが必要になります。照会はOLTP環境で実行されているので、LRUバッファーは表からのすべてのデータと、関連するバッファー管理オーバーヘッドで一杯になり、全員の1日の仕事(あるいは1週間、1カ月かもしれません)を台無しにしてしまいます。ハッシュ・テーブル・メモリーの所要量は非常に大きいので、一時スペースにオーバーフローした照会は、もし適正にセットアップされていなければ、他のすべての一時スペースも奪い去ってしまったかもしれません。数日間実行したのち、おそらくはとにかく照会を強制終了することになるでしょう。これがまさに、典型的な40日の照会の始まりです。

上に戻る

基本的なチューニングの手順
例の照会には、表のスキャン、2番目の表のスキャン、結合、という時間を要する3つの基本的な要素があります。ネストされたループが使用される場合(ここで大きいほうの表は、小さいほうの表のすべてのレコードの索引を使って読み取られます)、結合は2番目の読み取りの一部になります。ハッシュ結合からの結合は、2番目の表が読み取られている間に生じます。ほとんどのDSSでは、ハッシュ結合の使用が望ましくなります。

この照会をチューニングする際に、表スキャンおよび結合という2つの部分で進めると効果的です。最初に、以下の照会を使って、それぞれの表をスキャンするだけで要する時間のベンチマークを得ることにしましょう。こうすることで、照会時間を予測できるようになるだけでなく、照会がスロー・ダウンしたときに目立たせ、しかもかなりの速度で表を自ら読み取れるようになります。

SELECT CURRENT HOUR TO SECOND FROM SYSTABLES WHERE TABID = 1;
SELECT SUM(some_column) from tab1;
SELECT CURRENT HOUR TO SECOND FROM SYSTABLES WHERE TABID = 1;
SELECT SUM(some_other_column) from tab2;
SELECT CURRENT HOUR TO SECOND FROM SYSTABLES WHERE TABID = 1;

この結果は、Table 1に17分、Table 2に20分を要し、毎秒約30,000行を示しています。これらの結果は挿入されます。実際の読み取り時間は、いずれの表も37分でした。

上に戻る

データの分散
最初のチューニングの目的は、読み取りパフォーマンスを高めることにあります。すべてのデータを1つのDB領域に収めて、エンジンは表全体を読み取るために1つのスレッドに制限されます。データが複数のDB領域に広がっている場合は、データベースはDB領域またはフラグメントあたり1つのスレッドを開始することができます。iploadを使用して、各表をアンロード(およびリロード)するジョブを生成して実行できます。次に、各表をドロップして、複数のディスクに断片化させて再作成し、それをリロードします。この特殊な場合では、断片化の式はあまり重要ではないので、ただラウンドロビンを使うことができます。1つのDB領域にある表の読み取りに20分かかる場合には、表を4つのDB領域に広げるとスキャン時間が約5分に短縮されます。8つのDB領域に表を分散させて広げると、時間が約2.5分に短縮されます。

上に戻る

PDQPRIORITYにより並列を使用可能に
表を修正したので、最も明白な次のステップはPDQPRIORITYです。PDQPRIORITYをオフにすると、Informix Dynamic Serverは何も並列では行っていません。1つのスレッドがすべての読み取りを行っています。データをディスクから読み取るのに5分かかり、表あたり16のDB領域があると想定すると、照会は表あたり80分のオーバーヘッドで自動的に開始します。単にPDQPRIORITYを1に設定することで、エンジンはディスクあたり1つの読み取りスレッドを開始することができます(ご心配なく、同時に両方の表を読み取ろうとはしませんから)。

注: 
XPSでは並列スキャンは自動です。8.31およびダイナミック・メモリー割り振りでは、PDQPRIORITYはその重要性を一部失って、鉄則というよりはむしろ指針のようになっています。

結果: 
Table 1では3分21秒、Table 2では3分52秒で、毎秒約150,000行になります。 データを分断化するだけで、表スキャン時間は5分の1に短縮されます。しかし、まだ改善の余地はあるのです。

上に戻る

バッファー・オーバーヘッドを解消するためのライトスキャンの強制
OLTP環境では、キャッシュの読み取りおよび書き込み速度を最大限にしたいものです。これを行うには、必要な行をバッファー・キャッシュで見つけ、ディスクから読み取らなくてよいようにしたいものです。OLTPバッファー・キャッシュ管理には、何らかの最小オーバーヘッドがあります。ただし、表全体をスキャンする場合のDSS環境では、データがキャッシュにある確率は激減します。DSSバッファー・キャッシュの管理に関連するオーバーヘッドが厄介なものになり、DSS照会において重要な要素であるライトスキャンが必要とされるようになります。

ライトスキャンはDSS照会用に設計されています。ライトスキャンは、通常の常駐メモリー・バッファー・キャッシュを使用するのではなく、オーバーヘッドのはるかに少ない独自のライトスキャン・バッファーを使用します。照会は各自バッファー・プールのセットを確保します。そのため、読み取り速度に劇的な効果が得られます。

ライトスキャンを強制するための秘訣は、読み取り対象の表が常駐メモリー・バッファー・サイズよりも大きくなるようにし、ISOLATIONモードを共用表ロックでDIRTY READ, OR Repeatable/Commiteed Read Isolation Levelに設定することです。また、環境変数(エクスポートLIGHT_SCANS=FORCE)を設定することもできます。(IDSでは、ライトスキャンはvarcharsを持つ表に対して使用可能にできません。)

割り当てられるライトスキャン・バッファーの数は、RA(先読み)設定の係数です。Informix Masters Seriesのトレーニング・マニュアルでは、次のようになっています。

Light_scan_buffers = roundup (( RA_PAGES + RA_THRESHOLD) /
(MAXAIOSIZE/PAGESIZE))

MAXAIOSIZEは各ポートの定数です。一般には16です。ただし、HP、SolarisおよびSequentでは、それぞれ60、60、64となっています。

RA_PAGESおよびRA_THRESHOLDの値をそれぞれ最大の128および120まで上げると、さらにライトスキャン・バッファー・プールが増えて、望ましい状態になります。

ライトスキャン・バッファーに対応できるよう、仮想セグメント(SHMVIRTSIZE)も大きくするようにしてください。

以前のベンチマークの実行では、エンジンはライトスキャンを使用しませんでした(ライトスキャンのパフォーマンスへの効果を示すため、意図的にそうしました)。エンジンでUPDATE STATISTICSを実行することにより、最適化プログラムは例の照会で読み取られている表のほうが常駐メモリー・バッファー・プールよりも大きいことを認識して、再びベンチマークが実行されるときにはライトスキャンを使用します。

UPDATE STATISTICSはもちろん、ライトスキャンを機能させるカギですが、それは一体どの程度でしょうか。UPDATE STATISTICS mediumまたはhighは、大規模なデータウェアハウスで何時間も、場合によっては何日も、稼働する傾向があります。その結果、索引付き結合またはフィルタリングを行う場合に非常に便利になりますが、このハッシュ結合の場合には、とにかく表全体のスキャンに目を配ることになるので、迅速なUPDATE STATISTICS lowを実行して、後はハッシュ結合に任せます。環境変数DBUPSPACEを設定すると、UPDATE STATISTICSを実行するために必要な時間を大幅に短縮することができます。その他UPDATE STATISTICSのチューニングに関するヒントは、

http://www7b.boulder.ibm.com/dmdd/zones/informix/library/techarticle/miller/0203miller.html

のサイトを参照してください。

ベンチマーク照会を再び実行すると、結果はTable 1では43秒、Table 2では50秒となり、毎秒約700,000行になります。

私は、32フラグメントの表で最大毎秒200万行のスキャン速度を経験したことがあります。表をさらに多くのDB領域に断片化してこの読み取り速度を高めることもできますが、はたして照会時間をさらに20〜40秒縮めることが管理上の頭痛の種に値するものでしょうか。決めるのは皆さんです。割り振ることのできるフラグメントの数は、特定の断片化スキームに対するビジネス要件など、他の要因の制約を受ける場合もあります。この例ではさしあたり、以上の結果で十分といえます。

私は、CPUVPあたり3つのスキャン・スレッドの制限があると告げられました。したがって、DB領域の数の上限は3 x CPUVPということになります。

現在PDQPRIORITY=1設定を保持している照会を返すとするならば、得られる結果にはさほど大きな相違はないでしょう。表のスキャンはより迅速になりますが、照会には依然としてメモリーが不足してほとんど息も絶え絶えの状態です。

次のチューニング作業は、結合そのものに迫ります。いくつかのオプションも利用できます。まず、索引を使用してネストされたループ決増で照会を実行してみましょう。

索引の追加の検討
索引を追加することにより、ネストされたループ結合をセットアップする、あるいは読み取り対象データのサイズを索引ページだけに制限することのいずれかを行うことになります。言い換えれば、成功させる必要のあるものは何でも、索引に入れようとしていることになります。

索引を追加することで、最適化プログラムにデータに到達できる別のパスをもたらします。表の1つが十分に小さいと認識すれば、最適化プログラムはその表を読み取ることを選び、すべての行に対して大きい表をプローブして(索引を使用)結合を実行します。問題となる点は、エンジンがプローブを実行できる速度で、秒あたりの千単位で測定されます。3,000 行/秒で3,000万行というのは、ここで使用しているマシンでは楽観的なものですが、これは10,000秒、つまり約3時間にもなります。これではあまりに遅すぎます。索引を断片化して、それを表から切り離しても、ネストされたループをもっと速くするのに十分な劇的変化をプローブ速度にもたらしはしません。ですから、この場合には索引を追加しても何の足しにもならないばかりか、最適化プログラムを混乱させれば実害の及ぶおそれもあります。

動的ハッシュ結合を試してみましょう。

ハッシュ結合の強制
ハッシュ結合では、2つの表の小さいほうがスキャンされてメモリー内のハッシュ・テーブルに保管されます。メモリーが一杯になると(表が小さい場合以外は可能性があります)、このハッシュ・テーブルは一時ディスクにスワップされます。最初の表が読み取られた後、2番目の表が読み取られてハッシュ・テーブルに突き合わせされます。照会は、一時ディスクに置かれているハッシュ・テーブル・エントリーに定期的にスワップする必要があります。この一時ディスクとメモリー間スワップは高価で、しかも一般にハッシュ結合でも最も遅い部分です。したがって、ハッシュ結合に十分なメモリーを割り振ることは非常に重要になります。

PDQPRIORITYは、メモリー割り振りを制御します。究極のPDQPRIORITY設定とは、(PDQPRIORITY (PDQ_PRIORITY/100 * MAX_PDQPRIORITY/100) * DS_TOTAL_MEMORYです。1.5GBのDS_TOTAL_MEMORYがあり、そのうち1%を占める(PDQPRIORITY=1)場合には、ハッシュ・テーブルを構築するために15MBのメモリーがあります。15MB/20バイト(ハッシュ・テーブル・エントリー) = 786,000ほどのエントリーになります。これは大まかな推測ですが、これほど多くのエントリーの後に、データベースはハッシュ・テーブル・エントリーを一時ディスクに押し出さなければなりません。16のスキャン・スレッドで、データベースはおそらく毎秒約160,000行を読み込んでいることになりますから、5秒でハッシュ・テーブルが満杯になって上記のメモリー割り振りにより一時ディスクに押し出す必要が生じます。

ハッシュ・テーブルのサイズを求める実際の公式は、32 + keysize(キーサイズ) + rowsize(行サイズ)」です。したがって、ハッシュ・テーブル・サイズは32 + 2 + 155、つまりtab1レコードあたり189バイトとなります。* 2,900万 = 5.4GB ここでの重要な改善点は、tab1レコードのサイズを低下させて、ハッシュ・テーブルのサイズを減らすことでしょう。私は、計算の「rowsize」が検索された列のrowsizeであると知らされていましたが、いまだそれを実証しようとしたことがありません。

注目すべき最初のことは、ハッシュ・テーブルを構築すると実際にスキャン速度を毎秒約 300,000行も低下させるという点です(結果には変動があります)。

40 PDQでは、この照会は22分12秒かかり、マッチ率は毎秒約40,000行でした。メモリー量が増えると、一時ディスクとの間のスワッピングの量を低下できるだけではなく、メモリー内のハッシュ・テーブルのサイズを増やして秒あたりのマッチ率も高められます。

ハッシュ結合方式を活用することで、例の照会は、約5.5分という予測合計に対して毎秒約90,000行(80 PDQ)で実行することが明らかになっています。これは、ハッシュ・テーブル全体がメモリーに収まらずに照会が表の一部をディスクからスワップしなければならなくなるという大いなる例外といえます。そのためこの照会の実際の結果が8分40秒になっています(PDQ = 80)。照会の突き合わせフェーズで、マッチ率は90,000行/秒でした。1,700万の突き合わせの後、データベースはスワッピングを開始しました。10の合計スワップを実行し、各スワップは約10秒の長さでした(100秒、つまり1分40秒)。表全体がメモリーに収まらなかったために、照会も90,000行/秒の速度を維持することができませんでした。そのため(スワップ時間なしで)照会が(最初に予測されたように)5.5分ではなく7分で実行します。ハッシュ結合に十分なメモリーがある場合には、スワップする必要はなくなります。

場合によっては、よかれと考えた意図にもかかわらず、最適化プログラムのヒントを使用したにもかかわらず、最適化プログラムはもどかしそうに照会をネストされたループ・パスを押し下げ続けることもあります。このような場合には、なにか索引で見つけ出せないようなものを示して、最適化プログラムをハッシュ結合へと巧みに導くこともできます。

SELECT …
  FROM tab1, tab2
 WHERE tab1.key + 0 = tab2.key + 0

「+ 0」は高価ではなく、しかも照会をハッシュ結合として実行するよう強制します。これは、tab1.keyが索引として最適化プログラムに認識できなくなっているためです。

上に戻る

一時ディスク
驚くにはあたりませんが、一時ディスクは通常の表と同様の方法で、フラグメントあたり1スレッドで書き込み/読み取りが行われます。したがって、読み取り/書き込みスレッドを最大化するように複数の一時ディスクをセットアップしておくことが重要になります。一時ディスクにも複数のDB領域を用意しておくことが最善の策です。私は、上記の照会で使われたようにCPUごとに2つの一時DB領域をお勧めします。ただし、ある程度の複数を維持しておく限り、CPUあたり3つの一時DB領域まで増やすことができます。

バランス
プロセッサー間のタスクのスワッピングを最小に抑えられるように、搭載しているプロセッサー数と行う操作とのバランスをとることが重要です。ここでの例では、表は9および 11のDB領域に配置されていますが、これはつまり8つのプロセッサーがある場合には9つのスキャン・スレッドを、別の場合には11のスキャン・スレッドを処理していたということです。各プロセッサーはスキャン・スレッド間にスワッピングして、おそらくは時間を無駄にしていました。ビジネス要件はかならずしも常に技術的に道理に適うことと一致するわけではありません。

モデル変更
意思決定支援システムをさらに効果的にサポートするため、onconfigファイルにはいくつか変更を加えることができます。以下の構成は、Informix Masters Seriesトレーニング・マニュアルに掲載されているものです。

BUFFERS Low 2000
SHMVIRTSIZE High 使用可能メモリーの75%
SHMADD いずれでもよい 32000
SHMTOTAL Maximize 使用可能メモリーに設定
RA_PAGES Maximize 128
RA_THRESHOLD Maximize 120
DS_TOTAL_MEMORY Maximize 使用可能メモリーの90%

実のところ私は、BUFFERSを20000にし、必要に応じてOLTPアクティビティーをサポートするように設定するほうを選んでいます。表は十分な大きさがあるのでバッファーをあらゆる場合に小さく見せ、データにOLTPスタイルの作業が行われます。

まとめ
この記事で説明している例と類似したDSS照会には、3つの基本的な時間を要するコンポーネントがあります。それは、Table 1の読み取り、Table 2の読み取り、そして結合ステップです。照会がある種のフィルター(たとえば、100と200の間のキーなど)で選択ステートメントを制限する場合、読み取りコンポーネントは索引で処理しやすくなります。たとえば、これらの行のTable 1を読み取り、一致する行のTable 2をプローブしますが、実際の結合コンポーネントはプローブ中に発生します。

照会がフィルターで制限されない場合には、2つの表の完全な結合が必要になります。これは、索引読み取りで行うと極めて非効率的です。2つの表をスキャンして、メモリー内で結合を行うほうがはるかに効率的になります。ハッシュ結合は、これを行う理想的な方法です。したがって、チューニングは照会の3つの段階(scan1、scan2、および結合そのもの)を短縮する問題となります。ライトスキャンにより、データベースはハッシュ・テーブルの構築中に最初の表から読み取りを行うことができます。2番目の表で、データベースは、ほんのわずかだけ遅れをとって読み取ることができ、ハッシュ結合を含めます。さらに十分に高いPDQPRIORITYにより、データベースは、ハッシュ・テーブルから一時ディスクにスワップして時間を無駄にすることなくメモリー内でハッシュ結合を行える十分なメモリーを確保します。ハッシュ・テーブルが一時ディスクに押し出された場合、一時ディスクが良好に配置されていればさほど高価にはなりません。

読み取りロードを複数のディスクに分散して、十分なメモリーを予約し、十分な一時ディスクをとっておくことができる場合には、5分のウィンドウ内で各表をスキャンし、さらに1、2分で結合をまとめて、どのような照会時間も短縮することができます。照会に40時間、あるいは40日もかかるようであれば、先に述べた状況は実現していないのです。

執筆者について

Jack Parkerは、16年にわたりInformixベースのソリューションの構築と管理に取り組んでいるシステム・アーキテクトです。ここ7年間は、データ・ウェアハウジング業界に携わっています。不定期でcomp.databases.informixのライター、後援者、寄稿者を務めています。彼は、南ニューハンプシャーのコンサルティング会社Arten Technology Groupのパートナーです。
メール・アドレス:jparker@artentech.com

IBM、DB2、および WebSphere は、米国およびその他の国におけるIBM Corporation の商標です。
Windows および Windows NT は、米国およびその他の国における Microsoft Corporation の商標です。
Java および Java ベースの商標およびロゴは、米国およびその他の国における Sun Microsystems, Inc. の商標です。
その他の会社名、製品名、およびサービス名は、それぞれの商標またはサービス・マークです。 。

原文はこちら

上に戻る