前回のおさらい

前回、正体不明のパフォーマンス悪化の謎を解き明かせ!では、アプリケーション開発者のAさんからの相談に対して、V$ACTIVE_SESSION_HISTORY による Oracle Databaseの「血液検査」で見事パフォーマンス問題を解決したというお話をしました。

どんな内容だったかなぁ?というかたは、上記リンクから改めて確認してくださいね。

監視オペレータからの連絡

システムの「心臓」こと、Oracle Databaseに日々携わるたろーちゃん。
一日の業務を終え、帰り支度をしているたろーちゃんに、監視オペレータから電話が掛かってきました。
さて、何があったのでしょうか?

監視オペレータ「Oracle Database のサーバで、CPU使用率85%以上の状態が15分継続しています。ご確認をお願いします。」

 


ここのシステムでは、CPU使用率85%以上の状態が15分続くと、監視オペレータから連絡が来るようになっています。
さて、何が起こっているのでしょうか?

たろー「あれ?このサーバは、Aさんのシステムで使っている Oracle Database だな。
Aさん、まだ会社にいるかな?」
※Aさんのシステムについては以前の『正体不明のパフォーマンス悪化の謎を解き明かせ!』の回を参照して下さい。

 


たろーちゃんは、まずOSリソース状況から、Oracle Database 関連のプロセスがCPUを消費しているのを確認しました。次に、AWRレポートを使用して、負荷の高いSQLを発見しました。今まで見たことのないSQLだったので、たろーちゃんはAさんに確認することにしました。

 

Aさん「なんだい、たろーちゃん。何かあった?」

たろー「さっき監視オペレータからデータベースのCPU使用率が高いって連絡があったんだ。AWRレポートを調べてみると、このSQLがCPUに負荷を掛けてることが分かったんだけど、見たことのないSQLでさ。Aさん何か知ってる?」

Aさん「これは・・・。今朝リリースした機能のSQLだな。」

たろー「今朝?」

Aさん「うん。前にも話したけど、このWEBシステムは夜22時がアクセスのピークで、朝4時が一番アクセスが少ないんだ。だから、リリース作業を今日の朝4時に行ったんだよ。」

たろー「だから見たことのないSQLだったのか。」

Aさん「夜になって徐々にアクセスが増えてきたから、今になってCPU監視に引っ掛かったんだな。」

たろー「なるほど・・・。ちょっと実行計画を見てみるか。」

 

経過: 00:00:03.38  

実行計画
----------------------------------------------------------
Plan hash value: 2621382011

-----------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name          | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |               |     1 |    95 |    59   (2)| 00:00:01 |
|   1 |  SORT AGGREGATE                     |               |     1 |    95 |            |          |
|   2 |   VIEW                              | VW_DAG_1      |     1 |    95 |    59   (2)| 00:00:01 |
|   3 |    HASH GROUP BY                    |               |     1 |    21 |    59   (2)| 00:00:01 |
|   4 |     VIEW                            | VM_NWVW_0     |     1 |    21 |    59   (2)| 00:00:01 |
|   5 |      HASH GROUP BY                  |               |     1 |   257 |    59   (2)| 00:00:01 |
|   6 |       NESTED LOOPS                  |               |       |       |            |          |
|   7 |        NESTED LOOPS                 |               |     1 |   257 |    58   (0)| 00:00:01 |
|   8 |         NESTED LOOPS                |               |     1 |   201 |    56   (0)| 00:00:01 |
|   9 |          NESTED LOOPS               |               |     1 |   164 |    54   (0)| 00:00:01 |
|* 10 |           TABLE ACCESS FULL         | PARA8         |     1 |    26 |     3   (0)| 00:00:01 |
|* 11 |           TABLE ACCESS FULL         | KEKKA         |     1 |   138 |    51   (0)| 00:00:01 |
|* 12 |          TABLE ACCESS BY INDEX ROWID| KIHON         |     1 |    37 |     2   (0)| 00:00:01 |
|* 13 |           INDEX RANGE SCAN          | KIHON_KEY     |     1 |       |     1   (0)| 00:00:01 |
|* 14 |         INDEX RANGE SCAN            | PARA8_KEY     |     1 |       |     1   (0)| 00:00:01 |
|* 15 |        TABLE ACCESS BY INDEX ROWID  | PARA5         |     1 |    56 |     2   (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------

統計
----------------------------------------------------------
       3080  recursive calls
          0  db block gets
       9097  consistent gets
          0  physical reads
          0  redo size
        721  bytes sent via SQL*Net to client
        524  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed  

 

たろー1実行あたり 3.38 秒か。WEBシステムにしては長い時間だな。physical reads(ディスクから読み込んだブロック数) が0だから全てバッファキャッシュにヒットしてるんだろうけど、その分、CPUパワーが必要になる。『塵も積もれば山となる』で、アクセスが増加してきて今の状態になったんだな。」

Aさん「むむむ・・・。どうしたらいいかな?」

たろー「ぱっと見て、気になるのは NESTED LOOPS 結合TABLE ACCESS FULL が発生している箇所だ。」

 

<再掲>
-----------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name          | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |               |     1 |    95 |    59   (2)| 00:00:01 |
|   1 |  SORT AGGREGATE                     |               |     1 |    95 |            |          |
|   2 |   VIEW                              | VW_DAG_1      |     1 |    95 |    59   (2)| 00:00:01 |
|   3 |    HASH GROUP BY                    |               |     1 |    21 |    59   (2)| 00:00:01 |
|   4 |     VIEW                            | VM_NWVW_0     |     1 |    21 |    59   (2)| 00:00:01 |
|   5 |      HASH GROUP BY                  |               |     1 |   257 |    59   (2)| 00:00:01 |
|   6 |       NESTED LOOPS                  |               |       |       |            |          |
|   7 |        NESTED LOOPS                 |               |     1 |   257 |    58   (0)| 00:00:01 |
|   8 |         NESTED LOOPS                |               |     1 |   201 |    56   (0)| 00:00:01 |
|   9 |          NESTED LOOPS               |               |     1 |   164 |    54   (0)| 00:00:01 |
|* 10 |           TABLE ACCESS FULL         | PARA8         |     1 |    26 |     3   (0)| 00:00:01 |
|* 11 |           TABLE ACCESS FULL         | KEKKA         |     1 |   138 |    51   (0)| 00:00:01 |
|* 12 |          TABLE ACCESS BY INDEX ROWID| KIHON         |     1 |    37 |     2   (0)| 00:00:01 |
|* 13 |           INDEX RANGE SCAN          | KIHON_KEY     |     1 |       |     1   (0)| 00:00:01 |
|* 14 |         INDEX RANGE SCAN            | PARA_KEY      |     1 |       |     1   (0)| 00:00:01 |
|* 15 |        TABLE ACCESS BY INDEX ROWID  | PARA5         |     1 |    56 |     2   (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------

 

たろー「テーブルの結合順序を変更して、HASH結合 にしたらどうだろうか? ヒント句 を埋め込んで、と。」

 

経過: 00:00:00.06

実行計画
----------------------------------------------------------
Plan hash value: 1287118567

-----------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name          | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |               |     1 |    95 |    60   (4)| 00:00:01 |
|   1 |  SORT AGGREGATE                     |               |     1 |    95 |            |          |
|   2 |   VIEW                              | VW_DAG_1      |     1 |    95 |    60   (4)| 00:00:01 |
|   3 |    HASH GROUP BY                    |               |     1 |    21 |    60   (4)| 00:00:01 |
|   4 |     VIEW                            | VM_NWVW_0     |     1 |    21 |    60   (4)| 00:00:01 |
|   5 |      HASH GROUP BY                  |               |     1 |   257 |    60   (4)| 00:00:01 |
|   6 |       NESTED LOOPS                  |               |       |       |            |          |
|   7 |        NESTED LOOPS                 |               |     1 |   257 |    59   (2)| 00:00:01 |
|   8 |         NESTED LOOPS                |               |     1 |   220 |    57   (2)| 00:00:01 |
|*  9 |          HASH JOIN                  |               |     1 |   194 |    55   (2)| 00:00:01 |
|* 10 |           TABLE ACCESS FULL         | PARA5         |     1 |    56 |     3   (0)| 00:00:01 |
|* 11 |           TABLE ACCESS FULL         | KEKKA         |  1006 |   135K|    51   (0)| 00:00:01 |
|* 12 |          TABLE ACCESS BY INDEX ROWID| PARA8         |     1 |    26 |     2   (0)| 00:00:01 |
|* 13 |           INDEX RANGE SCAN          | PARA8_KEY     |     1 |       |     1   (0)| 00:00:01 |
|* 14 |         INDEX RANGE SCAN            | SKIHON_KEY    |     1 |       |     1   (0)| 00:00:01 |
|* 15 |        TABLE ACCESS BY INDEX ROWID  | KIHON         |     1 |    37 |     2   (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------


統計
----------------------------------------------------------
        260  recursive calls
          0  db block gets
        294  consistent gets
          0  physical reads
          0  redo size
        721  bytes sent via SQL*Net to client
        523  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

 

たろー「ほら! 3.38 秒から0.06 秒になったよ。recursive calls3080から260に減ったし、consistent gets9097から 294 に減ったから、CPU負荷も減る筈だよ。」

Aさん「すごいな、たろーちゃん。」

たろー「とりあえず、アプリケーションから発行しているSQLに、このヒント句を埋め込んでよ。」

Aさん「・・・。」

たろー「どうしたの?」

Aさん「・・・すぐにアプリケーションの修正が出来ないんだ。」

たろー「え?!」

Aさん「ソースコードは社内の別サーバで厳重にバージョン管理されていて、俺にはそのソースコードに対するアクセス権限が無いんだ。アクセスできる人は、今日は早朝作業だったからもう帰っちゃったよ。」

たろー「なんだって?!」

Aさん「しかも、このアプリケーションは Java で作られていてさ。コンパイルされて生成された classファイルを反映するには、アプリケーションサーバの再起動も必要なんだ。アプリケーションサーバを再起動出来る人も帰っちゃった…。」

たろー(うわぁ………。)

たろー「このままじゃ、アクセスピークの22時が来たら、データベースは負荷に耐えられないよ。」

Aさん「どうしよう……。」

 

今回はここまで。
アクセスピークの22時まで、時間がない!
このままでは心臓に負荷が掛かりすぎて、心停止(=システムの死亡)が発生してしまう!
このピンチをどう切り抜けるのか?!

投稿者プロフィール

たろーちゃん
たろーちゃん
株式会社システムサポート インフラソリューション事業部に在籍するPlatinumホルダー。
Oracle Databaseのパフォーマンスチューニングを得意とする。
データベースは Oracle 以外興味がないという変わり者。
連載「心臓外科医の術式」を執筆。