ラベル チューニング の投稿を表示しています。 すべての投稿を表示
ラベル チューニング の投稿を表示しています。 すべての投稿を表示

Oracle インデックスは有効なのか

本件のタイトルが意味するところは2つあり、
1つはインデックスを作成してみたはいいもののそのインデックスが
オプティマイザで有効になっているか否かを確認する。
もう1つは高速化を狙ったインデックスが意味を成していないのではないか、というものである。
前者を確認するにはSQLの冒頭に「explain plan for」と記述して、
その後にOracleの実行計画を参照する。


SQL> explain plan for
  2  select
  3    *
  4  from
  5    ZZZ_TABLE
  6  where
  7    CLM01 = '001' AND
  8    CLM02 = '1';

解析されました。

SQL> 
SQL> @C:\oracle\product\10.2.0\db_2\RDBMS\ADMIN\UTLXPLS.SQL

PLAN_TABLE_OUTPUT                                                                                   
----------------------------------------------------------------------------------------------------
Plan hash value: 2565712139                                                                         
                                                                                                    
-------------------------------------------------------------------------------------------         
| Id  | Operation                   | Name        | Rows  | Bytes | Cost (%CPU)| Time     |         
-------------------------------------------------------------------------------------------         
|   0 | SELECT STATEMENT            |             |     1 |   510 |     1   (0)| 00:00:01 |         
|   1 |  TABLE ACCESS BY INDEX ROWID| ZZZ_TABLE   |     1 |   510 |     1   (0)| 00:00:01 |         
|*  2 |   INDEX UNIQUE SCAN         | ZZZ_TABLEI0 |     1 |       |     0   (0)| 00:00:01 |         
-------------------------------------------------------------------------------------------         
                                                                                                    
Predicate Information (identified by operation id):                                                 

PLAN_TABLE_OUTPUT                                                                                   
----------------------------------------------------------------------------------------------------
---------------------------------------------------                                                 
                                                                                                    
   2 - access("CLM01"='001' AND "CLM02"=1)                                        

14行が選択されました。




上記の例を見ると、実行計画に「INDEX UNIQUE SCAN」とあるが、
これはインデックスを利用したことを表していて、
これが「TABLE ACCESS FULL」となっていると、インデックスが利用されずに
テーブルを総なめしているということになる。
Oracleのオプティマイザの判断により、インデックスを利用したSQLを発行したにも
関わらずそのような結果になることもある。

では、Oracleのオプティマイザのジャッジは正しかったのか。
ヒント文を利用すれば、明示的にインデックス検索や全表検索が可能となる。

select
  /*+ INDEX(ZZZ_TABLE ZZZ_TABLEI0) */ *
from
  ZZZ_TABLE
where
  CLM01 = '001' AND
  CLM02 = '1';


select
  /*+ FULL ZZZ_TABLE */ *
from
  ZZZ_TABLE
where
  CLM01 = '001' AND
  CLM02 = '1';


Java GCチューニング

あるプロジェクトにて、いつものように朝出勤するとサポート担当者が
慌ただしく電話対応に追われていた。
話を伺ってみると、昨日未明にアプリケーションサーバがシステムダウンが発生したとのこと。
その場は、サーバをリブートしてシステムを復旧することができたが、
原因は調査しなくてはならない。
ログを解析すると、メモリリークが発生したことが原因なのはすぐに分かったが、
メモリリークが発生した原因が分からない。
夜間バッチの処理中にメモリリークが発生しているが、
日次処理であり今までは問題なく稼働していた。
急激にトランザクションデータが増加したわけでもない。

そもそも、メモリリークは何故発生するのだろうか。
Javaの一番の特徴は、GC(ガベージ・コレクション)にあると言っても過言ではない。
通常、開発者はメモリ管理を意識してコーディングする必要があるが、
Javaに関しては開発者に代わってJVMが任意のタイミングでGCを発生させてくれるので、
基本的に開発者はメモリ領域を意識せずにコーディングに専念できる。
他言語からJava開発にシフトしてきた開発者の中には、
メモリ管理をせずにはいられない人もいて、明示的にGCを発生させるコードを記述する人もいる。
その際は、使用したオブジェクトを初期化しないとGCの対象にはならない。
どういうことかと言うと、JVMは現時点で利用されているメモリが必要かどうか判断できない。
メモリに格納されているデータを全てクリアしてしまうと、
今後の処理で必要となるデータまで削除されてしまう可能性があるからである。
Webアプリケーションでは、アプリケーションレベル・セションレベルのデータを
保持しておかなくてはならない。
では、GCが発生する任意のタイミングとはいつなのか。
JDK1.2以降では、ヒープ領域としてNEW・OLDの各領域が用意されていて、
NEW領域がいっぱいになった段階でGC(ScavengeGC)が走る。
OLD領域には、ScavengeGCで32回生き残ったNEW領域のオブジェクトが移行する。
そして、OLD領域がいっぱいになるとFullGCが走る。
これがメモリリークの原因である。

今回の事例で何故メモリリークが発生したのか。
まず、アプリケーションサーバが起動したタイミングでシステム全体で利用するリソースは
アプリケーションレベルで保存されるので、後にOLD領域に移行することが確定する。
次に通常業務はトラフィックな状態になると、セションデータもOLD領域に移行しやすくなる。
さらに、日次業務である夜間バッチは大量データを処理する為、一時データもOLD領域に
移行しやすくなる。
日々、この状態が続くとOLD領域がいっぱいになり、FullGCが頻発して最終的には
OutOfMemoryでシステムダウンすることとなる。

メモリリークを回避するにはどうすればいいのか。
全体のヒープ領域を拡張するという方法も考えられるが、
コストが掛かる上に成果が上がらないということも考えられるので
安易に拡張すべきではない。
NEW領域のサイズを変更してはどうだろうか。
拡大させると性能を向上させるというメリットがあるが、
メモリ効率を低下させるというデメリットも併せ持つ。
最大限に活かすには、ヒープ全体の1/3~1/4が妥当である。

NEW領域のEdenとSurvivor(From,To)の比率を変えてみてはどうだろうか。
オブジェクトは、New:Eden→New:Survivor(From) →New:Survivor(To)→OLD
の経路を辿るのだが、Survivor領域が小さいとScavengeGCが32回走るまでに
OLD領域に移行してしまう。
アプリケーションの特性を考えて、この比率を変えていくのが得策である。

どうやって解消したのか。
GCのチューニングで解決するのがベストであるが、
同様の事象が発生することも懸念されるので定期的(業務時間外で日次or週次のタイミング)に
アプリケーションサーバをリブートするという運用で落ち着いた。
システム稼働当初は、チューニングを行った上でGCの挙動を監視する運用を怠ってはいけないと
痛感させられる出来事であった。

<実行オプション>
-Xms : 初期ヒープサイズ(全体)
-Xmx : 最大ヒープサイズ(全体)
-Xmn(-XX:NewSize) : New世代領域サイズ
-XX:MaxNewSize : New世代領域サイズ
-XX:NewRatio : New世代領域とOld世代領域の比率(Old世代領域/New世代領域)
-XX:SurvivorRatio : New世代領域とSurvivor領域の比率(Eden領域/From領域)
-XX:TargetSurvivorRatio : New世代領域GC後のFrom領域内オブジェクトの割合目標

-verbose:gc(-verbosegc) : GC情報(簡易)
-Xloggc:filename : GC情報(ファイルへ出力)
-XX:+PrintGCDetails : GC情報(詳細)
-Xverbosegc : GC情報(詳細)を出力(HP-UX JVMのみ)
-XX:+PrintTenuringDistribution : オブジェクトの年齢情報
-XX:+TraceGen0Time : New世代領域の累積GC時間、GC回数、平均GC時間
-XX:+TraceGen1Time : Old世代領域の累積GC時間、GC回数、平均GC時間
-XX:+PrintGCTimeStamps : GCのタイムスタンプ
-XX:+PrintHeapAtGC : GC前後の詳細なヒープ情報

<GCログ解析ツール>
                               GCViewer
                                




Eclipse Javaチューニング

数年前、とあるプロジェクトの結合テストという名目で福岡に出張したことがある。
ユニットテストは既に完了していて、純粋に結合テストを行う予定だった。
テスト仕様書は無論完成していて、テスト環境も先発隊が構築しているので、
ちょっとした旅行気分で臨んでいた。
その甘い考えは、初日で早くも崩れ去るのであった。
画面から業務データを作成して、ワークフローが正常に流れることを確認しようとしたのだが、
初っ端の登録の段階で早くもシステムエラーが発生。
ログを解析すると、OutOfMemoryなるメッセージを発している。
たった、1トランザクションでメモリを食い潰すとはどんなコーディングをしているものか。
それに、ユニットテストの環境でも同様の事象が発生していたに違いない。
だが、そのプログラムのコーディング及びユニットテストを担当していた開発者は、
契約切れでそこにはいなかった。
已む無くデバッグすることになったのだが、
膨大なプログラムを一行ずつデバッグする訳にもいかないので、
Profilerを利用してメモリの計測をすることとなった。
いざ、実践してみるとメモリの使用量を表すグラフが急激に上昇しているのがハッキリと分かる。
例えるなら、倍々ゲームという言葉が一番適当かと。
その箇所をプログラムで確認すると、MapクラスのputAllを連発している。
putAllしか知らないのでは思ってしまうぐらいの勢いで
全て(※無駄なデータが大半)のデータの受け渡しをputAllで行っている。
メモリリークを起こして当然の結果である。
その担当者が作成したプログラムは全て同じ形式で記述されていた為、
回収を余儀なくされたのは言うまでもない。



<環境>
OS:Windows2000
JDK:jdk1.5
Eclipse:3.1
Tomcat:5.5
Tomcatプラグイン:Sysdeo Tomcat Launcher Plugin 3.2


1.Profilerプラグインをダウンロード

2.Eclipse3.1用パッチの適用
    profiler_jars.part1~4.rarをダウンロードし、解凍。
    profiler_trace.jar、profiler_ui.jarをProfilerフォルダへ上書きコピーする。


3.実行ライブラリの配置
    ProfilerフォルダにあるProfilerDLL.dllをJAVA_HOME\JRE\BIN\にコピー。

4.Eclipseの設定
    設定→Tomcat→JVMの設定→JVMパラメータの追加
      -D__PROFILER_TIMING_METHOD=1
      -D__PROFILER_USE_PACKAGE_FILTER=1
      -D__PROFILER_PACKAGE_FILTER=__M__sun.;__M__com.sun.;__M__java.;__M__javax.;__M__org.apache.;__P__jp.co.xxx ←業務パッケージ
      -XrunProfilerDLL:1




Oracle 統計情報

ある仕様変更の案件で、大量の業務データを一括で作成する要件があった。
また、ユーザからある程度のレスポンスを期待されていた。
以上のことから、処理方式をオンラインバッチ(擬似リアル)として、
業務処理をストアドプロシージャでの実装とする提案をした。
※業務ロジックは、基本的にJavaで実装されていたが、
JavaからDBにアクセスする際のオーバーヘッドが大きくなることを懸念して。
だが、出来上がったプログラムを実行しても処理が一向に終わらない。
すると、30分掛かってようやく終了。
こんなに時間が掛かるわけがないので、調査をしてみると、
「データが大量に増加したこと」
が起因していることが判明した。
Oracleは統計情報の収集を行っていて、
それを元に実行計画を立てている。
つまり、今回の事象を明示的に教えてやらなくてはいけないのだ。
統計情報を収集すると、僅か1分で処理が完了する結果となった。

                     ANALYZE TABLE テーブル名 COMPUTE STATISTICS;