Chapter 3 バッチ処理の方式(2)

ちょっと日が開いてしまいましたが、続きいきます。

1 バッチウィンドウ内での通常処理


オンラインと分割されたバッチウィンドウにおいて単純なバッチ処理が実行されるためには、「更新対象のデータがオンラインユーザーや他のバッチプロセスによって必要とならないこと」が求められる。同時実行性は問題とはならず、ただ1回だけのコミットがバッチ処理実行の最後に行われるだろう。

しかしながらほとんどの場合、もっと堅牢なアプローチを採用するほうが適切である。バッチシステムは時が経つにつれ複雑さと扱うデータ量の両方が大きくなっていく傾向がある、ということを理解しておかねばならない。ロック方式が設定されておらず、システムがまだ1つのコミットポイントにのみ依存しているような場合、バッチプログラムを修正することは多大な労力を伴うことになるだろう。そのため、最も単純なバッチ処理システムであっても、以降で説明するようなもっと複雑なバッチ処理時に検討すべき場合と同様に、再開-復帰操作ためのコミット処理の必要性を検討すべきである。

2 バッチ/オンライン処理の同時実行


複数のオンラインユーザーによって同時に更新されうるデータを処理するバッチアプリケーションは、オンラインユーザーが必要とするかもしれないデータ(データベースやファイル内の)に対して数秒以上の時間に渡るロックを獲得するべきではない。また、(バッチアプリケーションの)更新処理はトランザクションの処理の終了時点毎にコミットされるべきである。こうすることで、他のプロセスが利用できないデータが発生する可能性と、データが利用不可の状態になる経過時間とを最小化する。
物理的なロックを最小化するための他の手段としては、楽観的ロッキングパターン(Optimistic Locking Pattern)や悲観的ロッキングパターン(Pessimistic Locking Pattern)のどちらかを使って論理的な行レベルのロック処理を実装するというものがある。

楽観的ロック

レコードが競合する可能性が低い場面を想定している。典型的には、オンライン処理とバッチ処理の両方が同時に利用する各データベースのテーブルにタイムスタンプ列を追加するという手段をとる。アプリケーションが処理のために行を取得(フェッチ)するときには、そのタイムスタンプも取得する。それからアプリケーションが処理対象の行を更新するときには、WHERE節にオリジナルのタイムスタンプを利用して更新(UPDATE)する。もしタイムスタンプが一致すれば、データとタイムスタンプの更新が成功するように実装する。もしタイムスタンプが一致しなければ、それは自分が取得(フェッチ)して更新処理を行うまでの間に他のアプリケーションがその同一行を更新してしまっていることを示している。このため、更新処理は実行されない

悲観的ロック

レコードの競合が高い可能性で発生する場面を想定したロック戦略である。
このため、データの取得時に物理または論理的なロックを獲得する。悲観的論理ロックの1つのタイプに、データベースのテーブルに専用のロック列を使用するというものがある。アプリケーションが行を更新用に取得する際には、ロック列のフラグをオンにセットする。
フラグがたっている場合、他のアプリケーションはその同じ行を取得しようとするときにはそれをもとに論理的に失敗だと判定するようにするフラグをセットしたアプリケーションが行を更新すると同じようにフラグをクリアし、他のアプリケーションがその行を取得できるようにする。データの完全性は、たとえばDBロックを利用する(例:SELECT … FOR UPDATE)ことで最初の取得(フェッチ)とフラグのセットの間維持されねばならないということに注意。なお、この手法は、「ユーザーがレコードをロックしたままお昼ご飯に出掛けてしまったような場合に備えてロックを解放するためのタイムアウトカニズムを作りこむための苦労がややマシであるという点を除いて、物理ロックが持っているのと同じ欠点がある。

これらのパターンは実際にはバッチ処理には不向きであるが、バッチとオンライン処理の同時実行の場合には適用できるだろう(例:データベースが行レベルロックをサポートしていないような場合)。一般的なルールとして、楽観的ロックはオンラインアプリケーションに適しており、一方悲観的ロックはバッチアプリケーションに適している。論理的ロッキングを利用する場合には、(データの整合性のために)その論理ロックによって保護されるべきデータエンティティにアクセスする全てのアプリケーションがその同じ仕組みを利用しなければならない。

これらの両方とも、あるひとつのレコードのロックを解決するためのものである、ということに注意して欲しい。
というのも、しばしば我々は論理的に関連するレコードのグループをロックしなければならないようなことがある。物理的なロックを利用する場合は、デッドロックが起こらないように慎重にこれらを管理する必要がある。論理的なロックを利用する場合は、普通は“論理ロックマネージャ”が保護したい“論理的なレコード”のグループを理解するように作りこみ、平行性とデッドロックが発生しないことを保証する。この論理ロックマネージャは、通常は「競合についてのレポート」、「タイムアウトカニズム」などを実現するために独自のロック管理用テーブルを利用する。

3 並列処理

並列処理はバッチ全体としての処理時間を最小化するために、複数のバッチ処理/ジョブを並列で実行することができるようにする。これはジョブが同一のファイルやDBテーブル、インデックススペースなどを共有していない限りにおいて問題とはならない。

もしそうなら、この処理は区分化データを用いて実装すべきであろう。他の選択肢としては、制御テーブルを利用して依存性を維持管理するための基盤モジュールを作りこむという手段もある。制御テーブルはそれぞれの共有対象のリソースごとに1行のデータを持ち、あるアプリケーションがそのリソースを利用しているかどうかを管理する。バッチ基盤または並列ジョブとして動作するアプリケーションはそのテーブルから必要とするリソースが利用できるか否かを取得する形になる。

もしデータアクセスが問題とはならない場合、並列処理は並列処理するための追加のスレッドを利用するように実装することで実現できるだろう。メインフレーム環境では、伝統的には並列ジョブクラスというものが全プロセスに対する適切なCPU時間の保証のために利用されてきた。ともかく、この解決策には全ての実行プロセスにタイムスライスが渡されるように確実に保証できるだけの十分な堅牢性が必要になるということだ。

並列処理における、他の重要な課題は負荷分散やファイルやデータベースバッファといった一般的なシステムリソースの利用可能性(可用性)をどうするかという点が挙げられる。また、制御テーブル自体が容易く可用性の点でクリティカルなリソースになってしまうということも注意が必要である。