まず最初に ↓ の埋め込み SQL の RPG プログラムの実行例を見てみてください。IBM i V7R1 での実行です。
二回目以降の処理時間が 0 秒になっていますね。
ここで実行しているのは「伝統のRPG/ILE RPG/SQL RPG で一番遅いのは??」で紹介した埋め込み SQL の RPG プログラムです。SQLでデータベースにアクセスし、条件に応じた集計を行なって、結果を帳票として出力するプログラムです。こちらのCLをお借りして時間を計測しています。
処理時間が0秒だからといって実行されていないわけではありません。ちゃんと正しい結果の帳票が出ています。
何が起きているのでしょうか??
せっかくの計算結果を再利用しないのはもったいない!?
SQL も含めて、プログラムというものは入力に応じてその内容を処理し、計算するものです。
繰り返し実行したい処理を記述したものがプログラムです。何回実行しても同じ処理、同じ計算を行ってくれます。
処理が何回実行しても同じ、ということは、すべての入力が同じであれば同じ結果が出てくる、ということになりますね。
ここでちょっと考えてみましょう。
入力が同じである限りは結果が同じ、ということは、結果を保存しておけば、同じ入力が来たときにそれを使ってもいいわけです。処理時間がかからないのでレスポンスタイムは当然短くなります。プログラムのメインの処理・計算を省けるので、そのためのシステム資源(CPU 時間とか I/O 処理など)が節約できることにもなります。その資源で別の処理が実行できるので、システムのキャパシティの向上につながることになります。
実は、こうしたやり方(処理結果をとっておいて再利用するやり方)は「メモ化」といって、wiki にもあるように「プログラムの高速化のための最適化技法の一種」なんです。
確かに、結果がわかっているのにわざわざ同じ処理をもう一度し直すのは処理時間もシステム資源ももったいないですよね。別の処理に振り向けてやれば、システムはもっと有効に使え、全体としてシステムが処理できる量(キャパシティ)も多くなるわけです。
SQL では「メモ化」をデータベースがやってくれる!?
同様の考え方を SQL に適用したのが「結果キャッシュ」という機能です。SQL で「メモ化」」相当の処理はデータベース自体が行なってくれる、ということになります。もちろん、データベース製品が実装していれば、ですが。
SQL やファイル処理のプログラムの場合は、主な入力はファイル(テーブル)になります。選択条件などが引数として与えられる場合もあります。
アクセスするテーブルのデータに何の変更もなければ、選択条件も含めてまったく同じSQL文を実行すると毎回必ず同じ結果が返ってきます。
まったく同じSQL文は、アクセスするテーブルの内容が変わらなければ、何回実行しても同じ結果が返ってくるわけです。関数型言語での「関数」と同じ機能をSQL文が果たしていることになります。
この、「何回実行しても入力が同じであれば必ず同じ結果が得られる」ことが ↑ で述べた「メモ化」を適用するためには重要なことなんですね。
なぜでしょうか??
入力がすべて明示されていて、その入力がすべて同じであれば、同じ結果が必ず得られるわけです。
“明示されている入力すべてが同じ”だと確認できさえすれば、確実に結果を再利用できることになります。
“当たり前”と思われるかもしれませんが、必ずしもそうではないんです。
たとえば、プログラム内部でグローバル変数やワークファイルなど使っている場合は、そのプログラムが呼び出されるタイミングや状況によって返ってくる値は変わってくるでしょう。他のプログラムがその変数やワークファイルを更新してしまうかもしれないからです。更新されたのがプログラムの中で必要な情報である限り、結果に影響を与えてしまうはずです。
SQLの場合、“明示されている入力”はテーブルと選択条件としてWHERE句などにわたされる値の二種類だけです。他のプログラムから知らずに更新されてしまう”隠れた変数”や”ワークファイル”のようなものは存在しません。
そして、テーブルの最新状況も SQL 文も選択条件としての値もすべて、そもそもデータベースでSQL実行前に必ずチェックされているものです。データベースはこうした情報をすべて知っているんですね。
つまり、データベースが追加でひと工夫すれば SQL の「メモ化」は実現できる、ということなんですね。
「結果」さえ保存するようにすればデータベース側での「メモ化」は実現できる!?
整理してみましょう。
まず
- SQL が同じで、
- 処理対象のテーブルに何の変更もなくて、
- 選択条件などの引数が前回の実行と同じ
であれば結果は同じものになります。
通常の商用データベース製品であれば、SQL文も、テーブルの状態も、SQLに与えられた引数も、データベース内部ですべて情報を持っています。
たとえば、アクセス・プラン(実行計画)。アクセス・プランを作成するのは重い処理になるので、SQLが同じものであれば再利用するための仕組みが商用データベース製品にはあります。その、再利用できるかどうかの判断のために、SQL文もテーブルの状態も引数も判断材料として使われるようになっています。
となると足りないのは、
- 保存された処理結果
だけです。それを再利用するための判断材料となる情報は、すでにデータベースは持っているんです。
DB2 for IBM i の「結果キャッシュ」
DB2 for IBM i では V5R3 から「結果キャッシュ」という機能が利用できるようになりました。
これは ↑ で説明したような機能を実装したものです。つまり、実行結果を保管しておいて再利用できるようにしたわけです。
この記事の冒頭で見た「処理時間 0秒」は、この機能によるものなんですね。計算を省いて、保存されている結果を再利用しているので、処理時間が短かったんです。
この機能はデフォルトで有効になっています。↑ で見た結果も、V7R1 のデフォルトの設定下での実行によるものです。
V5R3の発表・出荷が 2003年なので、DB2 for IBM i がこのような機能を実装したのはかなり早い時期になります。元となるテーブルが更新されていないかどうか判断するのは、DB2 for IBM iは他のデータベース製品よりやりやすいのかもしれませんね。
IBM i の場合、「IBM i は「オブジェクト指向の OS」」で紹介したようにテーブルも”オブジェクト”です。”属性”を持っており、その”属性”の中に表統計なども含まれます。
IBM i の技術者であれば、DSPFD コマンドや DSPFFD コマンドでリアルタイムでの総行数、カラムやキーの状態、最終変更日時などがわかることを知っていますよね。この「結果キャッシュ」はジャーナルされてないテーブルに対しても有効なので、こうした”オブジェクト”としての”属性”を利用しているのではないかと考えられます。
何の属性の変更も指定しない CHGPF コマンドや RUNQRY コマンドによるデータの照会・表示などでは、キャッシュされている結果は無効にはなりません。処理時間は 0 秒のままです。
RGZPFM コマンドを実行すると、テーブルの再編成が行われますね。データの内容は変わりませんが、物理的な並び順などが変わる可能性がありますね。
よりよいアクセス・プラン(実行計画)を作ることができるかもしれないため、SQL文の再オプティマイズが行われます。
SQL文が再解析されるため、キャッシュされている結果は無効とされ、改めて処理が実行されることになります。
また、UPDATE して ROLLBACK した場合には、キャッシュされている結果は無効となり、再度処理が行なわれます。
↑ のSTRSQLコマンドのあいだに実行されている処理が↓です。
Oracle にも「結果キャッシュ」
Oracle にも「結果キャッシュ」という機能があります。
11gR1 からの機能で、Enterprise Edition にしかないものなので、それほど有名ではないかもしれませんが…
チュートリアルにも ↓ のような記述があります。DB2 for i の「結果キャッシュ」と同様のものと言っていいでしょう。
新しいSQL問合せリザルト・キャッシュを使用すると、リザルト・キャッシュ・メモリーと呼ばれる共有プールの領域に問合せと問合せの断片を明示的にキャッシュできます。 問合せが実行されると、リザルト・キャッシュが構築され、結果が返されます。 データベースは、後続の問合せ実行用にキャッシュされた結果を使用できます。このため、応答時間が向上します。 問合せでアクセスされるデータベース・オブジェクトのデータが変更されると、キャッシュされた問合せ結果は無効になります。
Oracle の場合は「共有プールの領域」というメモリー領域が使われますが、DB2 for IBM i の場合は単一レベル記憶といってメモリーとストレージの区別がありませんので、必ずしもメモリー領域が使われるわけではないところが大きな違いでしょうか。
Oracle の場合は、DB2 for IBM i のようにオブジェクト指向の設計になっておらず、処理対象となるテーブルの「属性」などを参照することはできません。
『Troubleshooting Oracle Performance』という本の p.599 に ↓ のように書いてありますが、
Invalidations take place when a table with dependent cache entries is involved in a transaction, not when the data which the cache entries are based on changes.
バッファ・キャッシュの中のテーブル情報を参照し、そこに何らかの変更があったときには取っておいた結果は無効になる、というロジックのようです。
「Oracle にも」と書いたので「何!?」と思われる方もいるかもしれませんが、↑ で述べたようにIBM i V5R3 の出荷が 2003年で Oracle 11gR1 の出荷が 2007年なので、DB2 for i の方が先に実装していることになりますね。
なので「にも」としました。
DB2 for IBM i ユーザーのみなさん、こんなふうに Oracle に先駆けて実装している機能もあるんですよ。自信を持ってくださいね~(笑)
データベースによる「メモ化」
同じ引数で、同じ内容のテーブルにアクセスする同一のSQLは、何回でも同じ結果を返します。
SQLが同じで、引数が同じで、テーブルが同じ内容だと判別がつけば、初回の実行の結果を再利用することができるわけです。再利用することで、二回目以降の実行は時間も短くなりますし、システム資源も使わないですみます。
もちろん、どんなテーブルものべつまくなしに更新が多いような環境では、この設定の効果がある場面は限られるでしょう。
それでも効果のあるウィンドウタイムが存在する可能性はあります。
通常はそんなにどんなテーブルもひっきりなしに更新される環境はあまりありません。更新の多いテーブルがあったり、更新の多い時間があったりするわけで、そうした時間以外はこの機能が効果を発揮する場面があるでしょう。(もちろん DB2 for IBM i でも Oracle でも)
何より、この効果を得るために犠牲にするものはありません。特に DB2 for i の場合はデフォルトの設定ですし、プログラムにも SQL にも何の変更も考慮も要りません。V5R3 から有効になっている機能で、いまや V7R2 が最新ですがメモリーやディスクの状況や使用量が悪化した、などといった副作用は報告されていません。
DB2 for i の場合は、設定をオフにしても再利用するための判断がオフにされるだけです。再利用のための結果を保存することは止められません。オフにしても、せっかく保存されている結果をただ単に再利用しないだけなので、それこそシステム資源の浪費になってしまいます。「何か気持ち悪い」などとワケのわからない理由でオフにしたりしないようにしましょう。オフにするのはデメリットしかないと思います。
なにより、これをオンにしておくことで「SQL の二度目の処理」にかかる負荷が大幅に減るわけです。レスポンスタイムも速くなりますし、空いた時間に別の処理をできることでピーク時のキャパシティに余裕ができる可能性があります。
オフにするということは、本来必要ない処理にシステム資源を使わせるようにしてしまうわけで、そのままシステムの金額にも跳ね返ってくることになります。忘れないようにしましょうね。