「[[Open棟梁 wiki>https://opentouryo.osscons.jp]]」は、「[[Open棟梁Project>https://github.com/OpenTouryoProject/]]」,「[[OSSコンソーシアム .NET開発基盤部会>https://www.osscons.jp/dotNetDevelopmentInfrastructure/]]」によって運営されています。 -戻る --[[アプリケーション設計のポイント]] ---[[データアクセス]] *目次 [#m42eecfa] #contents *概要 [#ka1eeebb] バッチEXEの実装方式 *注意点 [#e5b79740] バッチ処理の方式を検討する上で以下の点に注意する。 -例えば1万件の処理を行うにあたり、1万件分のデータをメモリに保持するよう処理を実装する。~ すると、ページングによる性能劣化や、メモリ リーク、アウト オブ メモリ例外が発生する。 -シーケンシャル アクセスの中間ファイルを使う概念が無く、全てデータベースへの入出力で処理を行う。~ すると、無駄なDBMSとのプロセス間通信のラウンドトリップや、ランダム アクセスで性能が劣化する。 -トランザクション分割の概念がなく、1つの巨大なトランザクション処理を行う(ロング トランザクション)。~ すると、(ロング トランザクションでは)ロックを長時間ホールドしたり、ロールバックに長い時間がかかる。 -リランを行う概念がなく、1レコードでも失敗すると全ての処理が失敗する。~ すると、リラン時に、常に最初からの開始となってしまうため、~ 処理時間が長いバッチは時間内に処理を完了することができない可能性がある。 -並列処理前提のプログラムと単一処理前提のプログラムの違いが分からずに実装する。~ すると、後々、処理の多重化(システム リソースを使い切って処理時間を短縮すること)などのチューニングができなくなる。 -実行ファイルを分割する概念がなく、全て1つの実行ファイルで処理する。~ すると、タスクを組み合わせて1つのジョブにすることができなくなる。また、リランに時間がかかることもある。 *バッチ処理フレームワーク [#c9a75c63] Spring Batch を参考に方式を参考にして、上記の問題を起こさない、多重化&リラン可能なバッチ処理方式を説明する。~ Spring Batchの多重化&リランは、オンライン側を排他で止めておくことが前提で、反復不可、ファントムは発生しない前提とする。 **トランザクション範囲 [#t55db9f0] 反復不可、ファントムの問題は無い前提のため、トランザクション範囲は、下図の(処理 → Write)の範囲とする。 (コミット インターバル分、データを渡す) ↓ Read ────>(処理 ──> Write) ↑ │ └───────────┘loop **多重化方法 [#mea3ddb4] -バッチEXEを多重起動する(Javaと異なりマルチスレッド化する必要はない)。 -それぞれのバッチEXEが処理する結果セットを分割する。 -これには、バッチEXE起動時に結果セット取得に使用する~ 検索条件(分割キー、主キー範囲)のコマンドライン引数を変更する。 **リラン [#y183f3c4] -異常終了時は、所定のディレクトリに次回、再開の行番号を出力する。 -多重起動することを考慮すると下記を1つのファイルで管理すると良い。 --「結果セット取得に使用する検索条件」 --「再開の行番号(処理済みの行番号)」 **分散多重処理対応 [#r6b1c914] 複数のバッチ処理サーバ。 分散多重処理の状態の一元管理には、~ 別途管理データベースなどの利用が必要になることがあり大掛かりになる。 *バッチ処理のフロー [#d1cac2b3] 集計処理の場合、DBMSを使用しない場合は、コミットインターバルは不要。 #ref(BatchSequence.png,left,nowrap,バッチ処理のフロー) **参考 [#qc887c61] *オンラインバッチ処理方式 [#j8248c0c] Windows NT系カーネルでは、 -コマンドライン起動で --startコマンドを用いなければ同期実行 --startコマンドを用いれば非同期実行 >が可能である。 -Processクラスを使用する場合は、 --Startメソッドを用いれば非同期実行 --WaitForExitメソッドを用いれば同期実行 >となる。 なお、非同期実行を行う場合、連打できてしまうので、~ Semaphoreなどを使用して多重実行を防ぐ必要がある。 -参考 --MSDNライブラリ ---System.Diagnostics.Processクラス~ https://msdn.microsoft.com/ja-jp/library/system.diagnostics.process.aspx ---MSDNライブラリ > System.Threading.Semaphoreクラス~ http://msdn.microsoft.com/ja-jp/library/system.threading.semaphore.aspx **同期実行 [#l9f02e53] ASP.NETからの同期実行 & セマフォ を使用してバッチの多重度を制御することも可能であるが、~ これではオンライン処理が待機によりスレッドを占有してしまい同時実行性が低下する場合がある。 **非同期実行 [#d34f3d0c] 非同期実行では上記問題は発生しないが、場合によっては、~ 多重度・リトライ、結果通知などの制御・実装が必要となってくる。 -非同期実行で多重度・リトライなどの制御が必要になる場合は、~ キュー(ディスク、RDB、MQなど)を用意して多重度・リトライを制御することも可能である。 -実行結果の通知方法(プッシュ型通知、プル型通知)を検討しておく必要がある。 多重度・リトライ、結果通知などの制御・実装が必要となる場合、”Open棟梁”の[[非同期処理サービス]]を利用できる。 *データアクセス [#x39e878c] **大量データ [#be271701] 大凡、下記の選択肢がある。 ***[[バッチクエリ作成支援機能]] [#v0b8a9bd] ***配列バインドの実行方法 [#ze851f70] 配列バインドは、ODP.NETやHiRDBなど、ごく少数のデータ・プロバイダでサポートされている。 -サンプル・コード(ODP.NET) --SQL定義 INSERT INTO XXX(AAA, BBB, CCC) VALUES(:P1, :P2, :P3); --コード // 配列データを作成 object[] temp1 = new string[] { "aaa", "bbb", "ccc" }; object[] temp2 = new string[] { "aaa", "bbb", "ccc" }; object[] temp3 = new string[] { "aaa", "bbb", "ccc" }; // ODP.NETの配列バインドの場合は、ArrayBindCountを指定 ((DamOraOdp)this._dam).DamOracleCommand.ArrayBindCount = temp.Length; // ODP.NET配列バインドの場合は、OracleDbTypeの型情報が必要 ((BaseDam)this._dam).SetParameter("P1", temp1, OracleDbType.Varchar2); ((BaseDam)this._dam).SetParameter("P2", temp2, OracleDbType.Varchar2); ((BaseDam)this._dam).SetParameter("P3", temp3, OracleDbType.Varchar2); --参考 ---Oracle Data Provider for .NET開発者ガイド 11g リリース1(11.1.0.6.20) > OracleCommandオブジェクト > 配列バインド~ http://otndnld.oracle.co.jp/document/products/oracle11g/111/windows/E06104-01/featOraCommand.htm#i1007888 ---How To資料 : ODP.NETデータベース・コマンドへの配列のバインド~ http://otndnld.oracle.co.jp/tech/windows/odpnet/howto/04_arraybind/content.html ---ODP.NETファーストステップ(1)> ODP.NETでOracle固有の機能を活用する (4/4) - @IT > 配列バインド~ http://www.atmarkit.co.jp/fdb/rensai/odpdotnet01/odpdotnet04.html また、[[動的パラメタライズド・クエリ分析ツール]]も、この「配列バインド」をサポートしている。 ***性能を考慮した[[動的パラメタライズド・クエリ]]の連続実行方法 [#y92d7822] -[[動的パラメタライズド・クエリ]]は、XML編集を伴うので、~ XMLタグの数が多くなる場合(100タグ以上が目安)、性能が劣化することがある。 --このため列数が非常に多いテーブルでは更に問題となり易い。 --このようなテーブルに大して[[動的パラメタライズド・クエリ]]を~ 連続実行する場合、APサーバ側に負荷がかかり、ボトルネックになり易い。 ---大量データ更新を行うバッチ処理で問題となり易い。 ---自動生成されたUpdate文は、全カラム分のXMLタグを持つ。 -対策 --[[動的パラメタライズド・クエリ]]は一度実行すると、内部的には~ XML([[動的パラメタライズド・クエリ]])からSQL([[静的パラメタライズド・クエリ]])に変換される。 --このため、このSQL([[静的パラメタライズド・クエリ]])を保持するDamを再利用すれば、~ SQL([[静的パラメタライズド・クエリ]])の連続実行となり、性能向上を実現可能である。 --しかし、一度パラメタライズド・クエリを実行するとCommandオブジェクトにパラメタが設定されたままになるので、~ 各DamのCommandオブジェクト プロパティから各データプロバイダのCommandオブジェクトを取得して、~ Command.Parameters.Clear()メソッドを呼び出し、この後、パラメタを再設定すれば良い。 -サンプル・コード~ なお、この処理は、Commandオブジェクトのクリアが可能な[[自作Dao>D層(Dao)の種類#p684509d]]で実装する必要がある。 object obj; // 動的SQLを読み込む場合。 this.SetSqlByFile2("DaoShippers_D1_Insert.xml"); // この状態では動的SQL(IsDPQプロパティを確認)。 System.Diagnostics.Debug.WriteLine(this.GetDam().IsDPQ.ToString()); // 実行1回目 this.SetParameter("CompanyName", "1回目"); this.SetParameter("Phone", "1回目"); obj = this.ExecInsUpDel_NonQuery(); // 以降は静的SQLとなる(IsDPQプロパティを確認)。 System.Diagnostics.Debug.WriteLine(this.GetDam().IsDPQ.ToString()); // 前回の実行で設定したパラメタ(Command.Parameters)をクリア。 ((DamSqlSvr)this.GetDam()).DamSqlCommand.Parameters.Clear(); // 実行2回目 this.SetParameter("CompanyName", "2回目"); this.SetParameter("Phone", "2回目"); obj = this.ExecInsUpDel_NonQuery(); // 前回の実行で設定したパラメタ(Command.Parameters)をクリア。 ((DamSqlSvr)this.GetDam()).DamSqlCommand.Parameters.Clear(); // 実行3回目 // ・・・ **DataSet、DataTableを使用したバッチ更新方式 [#p13798bf] ***概要 [#g606b757] -一般的に、DataSet、DataTableを使用したバッチ更新処理は以下のように実装される。 --本フレームワークでも、この方式のバッチ更新処理をサポートする。 --[[自動生成Dao>D層(Dao)の種類#t389966a]]を利用して処理を実装すれば、実装は更に容易になる。 -DataSet、DataTableの「AcceptChanges」メソッドを実行し、DataSet、DataTableを編集する。~ (「Fill」メソッドで充填したDataSet、DataTableは「AcceptChanges」メソッド実行済みの状態になっている。) -編集結果は、DataRowの「RowState」プロパティから確認できる(追加、更新、削除のステータスを検出する)。~ --追加:DataRowState.Added --更新:DataRowState.Modified --削除:DataRowState.Deleted -楽観排他の実装をサポートするものに、以下の列挙型がある。 --DataRowVersionの「Original」列挙子(楽観排他に利用するオリジナル データを取得する) dr["(列名)", DataRowVersion.Original] (drはDataRowクラス) --Where句の全列に、このオリジナル データを、検索条件として付与する。 これらの機能を活用することで、DataSet、DataTableを使用したバッチ更新処理は、DataAdapterの自動生成に頼らずとも容易に開発可能である。また、本フレームワークの バッチ更新処理(C/S2層)のサンプル プログラムは、 「~ \root\programs\C#\Samples\2CS_sample\DenDaoAndBatUpd_sample」 に配置されているので、これを参考にするとわかり易い。 注意点は、複数ポストバックに跨ってデータ編集を行うため、編集中のDataSet、DataTableをSessionなどに保持する必要があることである。この場合、サーバ メモリの消費量などに注意が必要である。 ***補足 [#rb61c5c6] -.NETでは、CommandBuilderにより、バッチ更新用DataAdapterの自動生成がサポートされ、~ DataAdapterの「Update」メソッドにDataSet、DataTableを渡すことによりバッチ更新が可能。 -しかし、CommandBuilderには以下のような問題があり、本フレームワークでも、この方式はサポートしない。 --SqlCommandBuilder仕組みを知らないとコードを理解し難い。~ 仕組みがブラックボックス、この方式を採用している例が少ない。 --テーブル単位のSELECTにしか対応していない。~ JOINのSELECTでは利用できないため、全体の方式を統一できない。 --count = dataAdapter.Update(dtClient)で、~ どの行がタイムスタンプアンマッチになったか?などが把握できない。 --INSERT処理 ---IDENTITY属性のあるカラムもINSERT文に含めてしまう。 ---新規行を追加するだけの場合、SQLのINSERT文を発行した方が効率的。~ (参照処理を伴わない、更新処理全体に対して言える。) --楽観排他方式 ---全列を旧行バージョン情報で比較する楽観排他方式 ---性能的に問題(WHERE文に全カラムを含めるため)。 ---SQLトレースも上記のパラメタが多くなるので分かりにくくなる。 ---timestamp列を含んだ更新ロジックを生成する機能は、サポートされていない。 ---また、GETDATE関数などをSQLに含められないため、SQLで~ 柔軟に更新日付を付与したり、タイムスタンプを更新したりできない。 ※ ただし、マスタメンテなど限られた範囲で、CommandBuilderは有効。