レベル: 中級 Mark Richards, Director and Sr. Technical Architect, Collaborative Consulting, LLC
2009年 7月 22日 連載「トランザクション・ストラテジー」の最終回となるこの記事では、著者 Mark Richards がハイパフォーマンス・アプリケーションのためのトランザクション・ストラテジーを Java™ プラットフォームに実装する方法を説明します。このストラテジーによってアプリケーションは高速な処理を維持できると同時に、ある程度のデータの完全性と整合性をサポートすることができますが、これに伴うトレード・オフを認識しておく必要があります。
この連載ではこれまで、以下の 3 つのトランザクション・ストラテジーを実装する方法を学んできました。
最終回となる今回の記事では、かなりのハイパフォーマンスが要求されるアプリケーションに適したトランザクション・ストラテジーを紹介します。高並行性トランザクション・ストラテジーと同じく、パイパフォーマンス・トランザクション・ストラテジーにも検討しなければならないトレード・オフが伴います。
 |
この連載について
トランザクションはデータの品質、完全性、整合性を向上させてアプリケーションをより堅牢にします。しかし、Java アプリケーションに効果的なトランザクション処理を実装するのは容易な作業ではありません。トランザクション処理の実装には、設計がコーディングと同じく重要になってくるからです。この連載では、Mark Richards が読者の案内役となり、単純なアプリケーションからハイパフォーマンスのトランザクション処理に至るまでの様々な使用状況で功を奏するトランザクション・ストラテジーを設計する方法を説明します。
|
|
トランザクションは、データの完全性と整合性を高いレベルで実現させるために必要です。しかしその一方、トランザクションには、かなりのコストもかかります。つまり、トランザクションは貴重なリソースを消費するため、アプリケーションの速度を低下させる可能性があるということです。ミリ秒を争う高速アプリケーションの場合には、ハイパフォーマンス・トランザクション・ストラテジーを実装することによって、ACID (原子性 (アトミック性)、一貫性 (整合性)、独立性、耐久性) 特性をある程度まで維持することができます。この記事を読むとわかるように、ハイパフォーマンス・トランザクション・ストラテジーは他のトランザクション・ストラテジーほど堅牢ではありません。また、ハイパフォーマンス・アプリケーションが必要なすべての事例に最適な選択肢というわけでもありません。しかし、このストラテジーによって処理を最大限に高速化する一方で、ある程度のデータの完全性と整合性を維持できる場合があることは確かです。
ローカル・トランザクションと補正フレームワーク
 | EJB 3 でのローカル・トランザクション
EJB 3 セッション Bean でローカル・トランザクションを使用する場合、セッション Bean の先頭に @TransactionManagement(TransactionManagementType.BEAN) アノテーションを付けて、コンテナーにトランザクションを管理しないように指示します。このアノテーションにより、コンテナーのトランザクション・マネージャーはトランザクション処理を制御しなくなります。
|
|
データ永続化の観点からすると、データベースの更新操作を最大限に高速化する方法は、ローカル・トランザクションをデータベース・ストアード・プロシージャーと組み合わせて使用することです。ローカル・トランザクション (データベース・トランザクションとも呼ばれます) は、コンテナー環境ではなく、データベースによって管理されるトランザクションなので、Spring での @Transactional アノテーションや EJB 3 での @TransactionAttribute アノテーションのように、アプリケーションにトランザクション・ロジックをコーディングする必要はありません。
ストアード・プロシージャーが高速に実行される理由は、事前コンパイルされてデータベース・サーバーに常駐しているからです。しかし、ハイパフォーマンス・トランザクション・ストラテジーにストアード・プロシージャーは必要ありません。ストアード・プロシージャーの有効性とパフォーマンスはこれまで興味深い議論を呼んできました (「参考文献」に記載した「So, are Database Stored Procedures Good or Bad?」のリンクを参照)。ストアード・プロシージャーを使用すると、アプリケーションの移植性が低くなり、複雑さが増して全体的なアジリティーが低下するおそれがあります。ストアード・プロシージャーは一般に Java ベースの JDBC SQL 文よりも高速に実行されるため、移植性と保守よりパフォーマンスのほうが重要な場合には有効な選択肢となります。とは言うものの、お望みであればもちろんシンプルな SQL を使った JDBC ベースのフレームワークを使用するのでも構いません。
ローカル・トランザクションがそれほど高速であるのなら、誰もがローカル・トランザクションを使用してもよさそうなものですが、現実は違います。その主な理由は、接続受け渡し (connection passing) などの手法を使わない限り、従来の ACID トランザクション特性を維持できないためです。ローカル・トランザクションでは、データベース更新操作は 1 つの集合としてではなく、個々の作業単位として扱われます。また、ローカル・トランザクションには、Hibernate、TopLink、OpenJPA といった従来のオブジェクト・リレーショナル・マッピング (ORM) フレームワークでは使用できないという制約もあります。iBATIS または Spring JDBC などの JDBC ベースのフレームワークを使用するか (「参考文献」を参照)、あるいは自家製のデータ・アクセス・オブジェクト (DAO) フレームワークを使用するしかありません。
 |
接続受け渡し
接続受け渡しとは、何らかの堅牢なコンテナー・ベースのトランザクション・マネージャーがない場合に、Connection オブジェクトの autocommit フラグを false に設定し、それぞれのメソッド呼び出しの間でデータベース接続を次々に受け渡す手法です。接続を使用し終わった時点で、Connection オブジェクトの commit() を実行して変更をコミットするか、rollback() を実行して変更を取り消します。接続受け渡しを使用するのは一般的には良策とは言えません。その理由は主に、接続受け渡しをすることでコンテナー・ベースのトランザクション・マネージャーの役割を果たそうとしているものの、効率性は低くなり、エラーのリスクは大きくなるからです。接続受け渡しの手法を使用していることに気付いたら、プログラム型または宣言型のいずれかのトランザクション・モデルに切り替えてください。
|
|
ハイパフォーマンス・トランザクション・ストラテジーは、ローカル・トランザクションの使用に基づいています。けれども、ローカル・トランザクション・モデルが基本 ACID 特性をサポートしないとしたら、果たしてローカル・トランザクション・モデルに基づくトランザクション・ストラテジーは賢いストラテジーになり得るのでしょうか。その答えは、ハイパフォーマンス・トランザクション・ストラテジーは、補正フレームワークと併せてローカル・トランザクション・モデルを利用するという点にあります。これにより、それぞれのデータベース更新操作は独立しているものの、エラーが発生した場合でも、補正フレームワークが個々のトランザクションを取り消すことによってアトミック性と整合性の両方が確実になるのです。
図 1 に、補正フレームワークなしでローカル・トランザクションを使用するとどうなるかを示します。3 番目のストアード・プロシージャーが失敗すると、論理作業単位 (LUW) が終了します。この時点でデータベースには 3 つの更新のうち 2 つだけが適用されていることになるため、データベースは不整合の状態のままになってしまいます。
図 1. 補正フレームワークを使用しない場合のローカル・トランザクション
補正フレームワークが配備されている場合は、エラーが発生すると、正常に完了したトランザクションが取り消されるため、データベースの整合性は維持されます。図 2 は、図 1 と同じエラーが発生した場合に、補正フレームワークが配備されていると、どのように対処されるかを示す概念図です。正常に処理を完了したストアード・プロシージャーは、補正フレームワークに登録されることに注意してください。3 番目のストアード・プロシージャーが失敗すると、この補正スコープに含まれるすべての内容を取り消すように補正フレームワークに対して指示するイベントがトリガーされます。
図 2. 補正フレームワークを使用した場合のローカル・トランザクション
この手法は通称、緩和された ACID (relaxed ACID) と呼ばれています。この手法が標準的なトランザクション・ソリューションとして適用されるのは、プロセス・コレオグラフィーや Web サービスを使用するために BPEL (Business Process Execution Language) を使用したサービス指向アーキテクチャーにおいて、トランザクションが長時間実行される場合です。このような場合、トランザクションの作業単位が完了するまでに数分、数時間、場合によっては数日かかることもあります。これほど長い間、リソースをロックできると想定するのは非現実的です。さらに、特定の異機種混合プラットフォームでトランザクションを伝播したり、HTTP (Web サービスの場合) を介してトランザクションを伝播したりするのは容易なことではありません (時には不可能です)。
緩和された ACID の概念は、短時間実行されるトランザクションにも適用できます。ハイパフォーマンス・トランザクション・ストラテジーの場合、トランザクションの実行時間は分単位ではなく、秒単位で測定されますが、極めてハイパフォーマンスのアプリケーションでも基本となる原則は同じです。つまり、データベースの並行性を最大限に高め、待機時間と処理時間を最小限にする必要があります。その上、データベース更新操作の実行時間をできる限り高速化する手段を活用する必要もあります。これを実現するのが、ローカル・トランザクションとデータベース・ストアード・プロシージャーを使用するという方法です。補正フレームワークは単に、エラーが発生したときに支援するためだけにあります。万事順調に運んでいるときに割り込むことはありません。データベースの並行性は最大限となり、データベース更新操作は最も速い方法で実行され、エラーが発生した場合には補正フレームワークがすべてを処理してくれます。そう聞くと天国のような話ですが、残念ながらいいことばかりではありません。
トレード・オフと問題
ハイパフォーマンス・トランザクション・ストラテジーには多くのトレード・オフと問題が伴います。前述のとおり、このストラテジーは最大限のトランザクション処理速度をもたらすと同時に、ある程度の ACID 特性を維持しますが、トランザクションの独立性、堅牢性、単純さはあきらめることになります。そのため、ハイパフォーマンス・トランザクション・ストラテジーを使用するのは、連載で説明した他の 3 つのストラテジーでは必要なパフォーマンスを実現できない場合のみでなければなりません。補正フレームワークは複雑であり、リスクを伴います。それは、自分で作成するにしても、オープンソースや商用ソリューションのフレームワークを入手して使用するにしても同じことです (もう少し後で説明します)。
ハイパフォーマンス・トランザクション・ストラテジーに関連する最大の問題は、堅牢性とデータの整合性が全般的に欠けていることでしょう。これは大抵の補正ベースのソリューションに言えることです。トランザクションとしての独立性がないことから、データベース更新操作のそれぞれが個別の作業単位として扱われます。そのため、別の作業単位が処理中のデータに作用しかねません。また、LUW の間にエラーが発生した場合、更新を取り消すにはもう手遅れです。つまり一般的には、更新の取り消しが問題の連鎖につながるということです。例えば、ある商品の在庫を使い果たすほどの大口の注文を処理しているとします。処理中に発生するイベントによって、(LUW の間に注文がデータベースにコミットされるため) 供給者に対してその商品の在庫補充メッセージが自動的に送信されます。しかしこの注文の処理中にエラーが発生した場合、補正フレームワークはトランザクションを取り消しますが、注文の結果 (つまり、在庫補充メッセージ) はすでに供給者に送信されているため、その特定商品の過剰在庫という事態に至ることになります。従来の ACID 特性が維持されていれば、在庫補充のイベント・メッセージは、注文処理の LUW 全体が完了するまで送信されません。この例は、補正フレームワークを使用してトランザクションのアトミック性を維持するとしても、データの不整合が発生する理由を説明する、数ある例の 1 つにすぎません。
ビジネス状況や技術によっては、ハイパフォーマンス・トランザクション・ストラテジーがそぐわない場合があります。特に、非同期処理のシナリオの場合、補正フレームワークと緩和された ACID を適用することで大きなリスクがもたらされます。場合によっては、ある程度のパフォーマンスを犠牲にして、速度に劣る ACID ベースのトランザクション・ストラテジーを適用しなければならないこともあります。また、ハイパフォーマンス・トランザクション・ストラテジーには、プログラム型または宣言型トランザクション・モデルのいずれかを必要とする ORM ベースの永続化フレームワークを利用できないというトレード・オフもあります。ただし、そのままの JDBC コードを処理しなければならないわけではありません。iBATIS (オープンソースの SQL マッピング・フレームワーク) および Spring JDBC をはじめ、使用できる JDBC ベースのフレームワークは豊富にあります。あるいは、DAO ベースの独自のカスタム・フレームワークを使用することも可能です。ORM に関する制約からは、さらに別のトレード・オフも受け入れざるを得なくなります。それは、このトランザクションをサポートすることによるパフォーマンスの向上と引き換えに、保守が容易でなくなること、そして技術を選択できなくなることです。
補正フレームワークを使用することによって、ある程度のデータベース整合性を維持できるとは言え、ハイパフォーマンス・トランザクション・ストラテジーには高いリスクが伴います。トランザクションの取り消しプロセス中にエラーが発生すると、データベースは不整合の状態のままになる可能性があります。この場合、データベース更新操作のなかには取り消されるものもあれば、取り消されないものもあるので、手動で問題を修正する必要が生じる可能性もあります。この理由から、ハイパフォーマンス・トランザクション・ストラテジーを適用するのにふさわしいアプリケーションは、単一の LUW にデータベース更新操作がほとんどないアプリケーションです。さらに、このストラテジーを適用するアプリケーションは一般的に、インターリーブする LUW 内で共有エンティティーを使用しないものです。つまり、複数のユーザーが同じエンティティー (アカウント、顧客、注文など) を同時に操作することがめったにないことを意味します。これらの特性により、トランザクションの整合性と独立性の欠如によって壊滅的結果が生じるという可能性は低くなります。
このハイパフォーマンス・トランザクション・ストラテジーという独特のトランザクション・ストラテジーに適したアプリケーションは、かなりの堅牢性があって、エラーが頻繁には発生しない (エラー率 10 パーセント未満) アプリケーションです。トランザクションの補正は、コストも時間もかかる操作です。データベース更新操作を頻繁に取り消していれば、システムの動作は遅くなり、他のトランザクション・ストラテジーのいずれかを使用した場合よりも全体的な処理速度が低下することになります。さらに、補正更新を実行する必要が多くなればなるほど、データベースに不整合が生じるリスクは大きくなります。このトランザクション・ストラテジーを選択する前には、必ずエラー率を分析してください。
この記事の残りの部分では、既存の補正フレームワークについて説明し、カスタム・ソリューションを使用した単純な実装で、これまで説明した概念を例証します。
既存の補正フレームワーク
 |
同じ概念で対処する異なる問題
補正フレームワークは通常、長時間実行されるトランザクション (L-R と呼ばれることもあります) と関連付けられます。補正フレームワークは、ビジネス・プロセス・サーバー (Microsoft BizTalk Server、Oracle WebLogic Integration、Oracle BPEL Process Manager、IBM WebSphere Process Serverなど) で汎用されているだけでなく、Web サービスの領域でのトランザクション関連の問題を解決するためのソリューションとしても使用されています。残念ながら、これらの補正フレームワークはハイパフォーマンス・トランザクション・ストラテジーの実装には適していません。ハイパフォーマンス・トランザクション・ストラテジーが対象とするのは、長時間実行されるトランザクションではなく、存続時間が短く、調整が必要な分離されたアクティビティーだからです。
|
|
Java プラットフォーム内で使用できる補正フレームワークには、J2EE Activity Service for Extended Transactions (JSR 95) と JBoss Business Activity Framework があります (「参考文献」を参照)。この 2 つのフレームワークは、登録機能、メッセージング機能、そして補正トリガー・ロジックを提供します (更新取り消しメソッドそのものは提供しません)。囲み記事「同じ概念で対処する異なる問題」で取り上げた補正フレームワークと同じく、これらのフレームワークは一般に長期間実行されるトランザクションか、Web ベースのリクエストに関連付けられるため、ハイパフォーマンス・トランザクション・ストラテジーで使用するのは困難です。その結果、このトランザクション・ストラテジーを使用するときにはほとんどの場合、独自のカスタム補正フレームワークを作成することになります。
J2EE Activity Service 仕様は主にアプリケーション・サーバー・ベンダーを対象にしていますが、これと同じ概念を独自のカスタム補正フレームワークにも適用することができます。したがって、このセクションでは皆さんに補正フレームワークがどのような動作をするのか理解してもらうため、J2EE Activity Service について簡単に紹介しようと思います。
J2EE Activity Service for Extended Transactions がベースとするのは、OMG Activity Service です (「参考文献」を参照)。J2EE Activity Service 仕様では、アクティビティー内でのアクションの実行を調整および制御する、一連のインターフェースとクラスを定義しています。アクティビティーとは、登録されたアクション (データベース更新操作など) のセットのことです。アクティビティーを制御、調整するコントローラーはプラガブル・プロトコルで、通常はサード・パーティーのプラグイン・コンポーネントとして実装されます。それぞれのアクティビティーにはシグナル・セット (javax.activity.SignalSet) が含まれ、このセットから各登録アクションにシグナル (javax.activity.Signal) が送信されます。図 3 は、補正を使用した場合の動作を示す概念図です。
図 3. J2EE Activity Service の概念図
アクティビティーはコントローラー (具体的に言うと、コントローラー内の補正マネージャー・コンポーネント) に登録される必要があります。アクティビティーが完了すると、コントローラーにシグナル (この場合、SUCCESS または FAILURE のいずれか) が送信されます。コントローラーがアクティビティーから SUCCESS シグナルを受信した場合は、コーディネーター・コンポーネントにシグナル (この場合は PROPAGATE) を送信し、それによって次のアクティビティーを呼び出します。図 3 のステップ 8 で、FAILURE シグナルがコントローラーに返されていることに注目してください。この場合、コントローラーはコーディネーターに FAILURE シグナルを送出することによって、補正アクティビティーを逆の順序で呼び出します。図 3 には図示されていませんが、コントローラーはさらに、取り消しアクティビティーが正常に完了することを確実にするため、補正アクティビティーとコーディネーターとの間で送受信されるシグナルもモニターします。
カスタム補正フレームワークの実装
カスタム補正フレームワークを作成すると聞くと、手強い作業のように思えますが、実際はそれほど複雑ではありません。ただ、時間がかかるだけのことです。独自の補正フレームワークは、従来の簡素な Java コードを使って実装することも、それより精巧な技術を使って実装することもできます。簡潔さを念頭に、ここでは簡素な Java コードを使用した単純な例を用いてカスタム補正フレームワークの概念を説明します。創意工夫を生かした実装の楽しみは、読者の皆さんにお任せします。
使用する補正フレームワークがオープンソースであろうと、商用であろうと、あるいは独自に作成するものであろうと、データベース更新操作を取り消すために呼び出すメソッド、SQL、またはストアード・プロシージャーを提供する必要があることには変わりありません。これが、ハイパフォーマンス・トランザクション・ストラテジーでストアード・プロシージャーを使用したいと思う、もう 1 つの理由です。ストアード・プロシージャーは、分類整理するのが比較的容易であり、しかも必要なものをすべて完備しています。ストアード・プロシージャーを使用すると、補正プロシージャーを識別 (そして実行) しやすくなります。そこで、これから説明する例では、ストアード・プロシージャーを使うことにします。
必要以上に詳細を説明して読者を飽きさせることのないように、データベースには以下のストアード・プロシージャーがすでに用意されているという前提にします。
sp_insert_trade (新しい株式取引注文をデータベースに挿入)
sp_insert_trade_comp (データベースで削除を実行することによって、取引挿入操作を取り消す)
sp_update_acct (株式売買を反映するために口座残高を更新)
sp_update_acct_comp (口座残高を最終更新操作以前の値に更新)
sp_get_acct (データベースから勘定を取得)
このストラテジーに最も深く関係するコードに専念できるよう、CallableStatement JDBC コードを使用した DAO クラスについての説明も省きます (純然たる JDBC を使用してストアード・プロシージャーを呼び出すための説明とコードについては、「参考文献」を参照してください)。カスタム補正コーディネーターの実装は千差万別で、かなり冗長になり得るので、ここではそのベースとなる構造だけを紹介し、実装コードの残りの部分を埋める方法についてはコメントで記載します。
ストラテジーをどのように実装するか、そして補正更新にどの技術を使用するかによって、更新取り消し操作の制御に使うアノテーションやロジックは、アプリケーションの API 層にあることもあれば、DAO 層にあることもあります。ハイパフォーマンス・トランザクション・ストラテジーを実装する手法を説明するため、ここではアプリケーションの API 層に補正スコープを調整するロジックがある単純な株式取引の例を使用します。この例で株式取引に関連付けられているアクティビティーは、データベースへの株式取引の挿入 (アクティビティー 1)、株式取引を反映するための口座残高の更新 (アクティビティー 2) です。この 2 つのアクティビティーは、ローカル・トランザクションとストアード・プロシージャーを使用した個別のメソッドに実装されています。補正スコープを管理して、エラーが発生した場合にアクティビティーを取り消すのは、カスタム補正コーディネーター (CompController) の役目です。
リスト 1 に、補正トランザクションを使用していない株式取引メソッドを記載します。processTrade() メソッドが参照する AcctDAO および TradeDAO クラスには、前にリストアップしたストアード・プロシージャーを実行するための JDBC ロジックが含まれます。これらのクラスについては、リストを簡潔にするために省略します。
リスト 1. 補正トランザクションを使用しない株式取引の例
public class TradingService {
private AcctDAO acctDao = new AcctDAO();
private TradeDAO tradeDao = new TradeDAO();
public void processTrade(TradeData trade) throws Exception {
try {
//adjust the account balance
AcctData acct = acctDao.getAcct(trade.getAcctId());
if (trade.getSide().equals("BUY")) {
acct.setBalance(acct.getBalance()
- (trade.getShares() * trade.getPrice()));
} else {
acct.setBalance(acct.getBalance()
+ (trade.getShares() * trade.getPrice()));
}
//insert the trade and update the account
long tradeId = tradeDao.insertTrade(trade);
acctDao.updateAcct(acct);
} catch (Exception up) {
throw up;
}
}
}
|
リスト 1 には、トランザクション管理 (プログラム型または宣言型トランザクションのアノテーションまたはコード) がないことに注意してください。updateAcct() メソッドの実行中にエラーが発生しても、insertTrade() メソッドによって挿入された取引はロールバックされないため、データベースに不整合が生じてしまいます。このコードは処理速度には優れているものの、ACID トランザクション特性をサポートしません。
ハイパフォーマンス・トランザクション・ストラテジーを適用するには、まず始めに補正フレームワークを作成 (または使用) し、このフレームワークを利用することで、アクティビティーの追跡と、エラーが発生した場合のアクティビティーの取り消しを行えるようにする必要があります。リスト 2 は、単純なカスタム補正コーディネーターの例です。この例から、独自のカスタム補正フレームワークを作成する際に必要なステップの概要がわかります。
リスト 2. カスタム補正フレームワークの例
public class CompController {
//contains activities and the callback method to reverse the activity
private Map compensationMap;
//contains a list of active compensation scopes and activity sequence numbers
private Map compensationScope;
public CompController() {
//load compensation map containing callback classes and methods
}
public void startScope(String compId) {
//send jms start message containing compId as JMSXGroupId
}
public void registerAction(String compId, String action, Object data) {
//send jms data message containing sequence number and data
//using compId as JMSXGroupID.
//CompController manages sequence number internally using the
//compensationScope buffer and stores in JMSXGroupSeq message field
}
public void stopScope(String compId) {
//consume and remove all messages having compId as the JMSXGroupID
//without taking action
//remove compId entries from compensationScope buffer
}
public void compensate(String compId) {
//consume all messages having compId as the JMSXGroupID and process in
//reverse order
//using the compensation map and reflection to invoke reversal methods
//remove compId entries from compensationScope buffer
}
}
|
compensationMap 属性には、全アクティビティー (の名前) とそれに対応する取り消しアクティビティーのクラスとメソッド (の名前) のリストが、あらかじめロードされたリストとして含まれています。この例の場合は、{"insertTrade", "TradeDAO.insertTradeComp"} と {"updateAcct", "AcctDAO.updateAcctComp"} が含まれることになります。compensationScope 属性に含まれるのは、アクティブな補正スコープの compId と、今までに登録されたアクティビティーのリストです。このバッファーは、registerAction() メソッドが使用する次のシーケンス番号を取得するために使用されます。残りのメソッドは、見てのとおりの内容です。
注意する点として、私は補正コーディネーターの実装に JMS (Java Message Service) メッセージングを使用しています。この技術を選んだ第 1 の理由は、補正中に障害が起きた場合、ロールバックできなかったトランザクションが JMS キューに残され、そのトランザクションを別のスレッドが取り出して実行できることを (永続化メッセージと保証付き配信を使用することで) 保証する手段となるからです。また、JMS メッセージングでは非同期のアクティビティー登録および補正処理が可能なので、アプリケーション・ソース・コードをさらに高速に処理することができます。当然、補正情報をメモリー内に保持すれば処理速度は大幅に向上しますが、補正コーディネーターが失敗した場合にはデータベースの不整合がさらに大きくなります。
リスト 3 に記載するソース・コードの例で、カスタム補正フレームワークをリスト 1 の元のアプリケーション・ソース・コードに適用する手法を説明します。
リスト 3. 補正フレームワークを使用した株式取引の例
public class TradingService {
private CompController compController = new CompController();
private AcctDAO acctDao = new AcctDAO();
private TradeDAO tradeDao = new TradeDAO();
public void processTrade(TradeData trade) throws Exception {
String compId = UUID.randomUUID().toString();
try {
//start the compensation scope
compController.startScope(compId);
//get the original account values and set the acct balance
AcctData acct = acctDao.getAcct(trade.getAcctId());
double oldBalance = acct.getBalance();
if (trade.getSide().equals("BUY")) {
acct.setBalance(acct.getBalance()
- (trade.getShares() * trade.getPrice()));
} else {
acct.setBalance(acct.getBalance()
+ (trade.getShares() * trade.getPrice()));
}
//insert the trade and update the account
long tradeId = tradeDao.insertTrade(trade);
compController.registerAction(compId, "insertTrade", tradeId);
acctDao.updateAcct(acct);
compController.registerAction(compId, "updateAcct", oldBalance);
//close the compensation scope
compController.stopScope(compId);
} catch (Exception up) {
//reverse the individual database operations
compController.compensate(compId);
throw up;
}
}
}
|
リスト 3 では、トランザクション作業単位を定義するときに、最初に startScope() メソッドを使用して補正スコープを開始していることに注意してください。これに続き、アクティビティーを登録するときに残高をコーディネーターに渡せるようにするため、元の残高を保存しておかなければなりません。アクティビティーが完了した時点で、registerAction() メソッドを使ってそのアクティビティーを登録します。すると補正コーディネーターは、データベース更新操作が正常に完了したこと、そして可能な補正アクティビティーのリストにこのデータベース更新操作のアクティビティーを追加する必要があることを認識します。LUW 全体が正常に終了したら、stopScope() メソッドを呼び出し、すべての参照を補正コーディネーターから削除します。一方、例外が発生した場合には compensate() メソッドを呼び出して、データベースにコミットされたアクティビティーを取り消します。
リスト 2 とリスト 3 のソース・コードは本番用のコードとはほど遠いものですが、独自の補正フレームワークを作成する手法を明らかに示しています。カスタム補正フレームワークでは、カスタム・アノテーションやアスペクト (インターセプター)、さらには独自のカスタム補正ドメイン特化言語 (DSL: Domain-Specific Language)(「参考文献」を参照) を使用して、さらに直観的なコードにすることもできます。補正フレームワークに JMS 非同期メッセージングを使用する必要はありませんが、補正の失敗を取り巻く問題に対処するには役立つと思います。
まとめ
ハイパフォーマンス・トランザクション・ストラテジーを使用するかどうかは、詰まるところ、トレード・オフの問題になります。このトランザクション・ストラテジーには多くのリスクが伴い、実装するには複雑です。しかし、エラーも無くかなり堅牢なアプリケーションがパフォーマンスを最優先事項としている場合、ハイパフォーマンス・トランザクション・ストラテジーは、データベースの完全性と整合性をある程度保証しつつ、パフォーマンスに悪影響を与えないストラテジーとして適しています。パフォーマンスが第一の懸念でないとしたら、このタイプのソリューションを勧めるかと言えば、もちろんお勧めしません。アプリケーションでは常に、従来の ACID 特性を適用するよう努めてください。その一方、パフォーマンスのためには、ある程度のデータベースの整合性とデータの完全性を犠牲にしても構わないというのであれば、ハイパフォーマンス・トランザクション・ストラテジーを検討してください。
この連載では、トランザクション処理に伴う落とし穴と課題について説明し、それぞれのアプリケーションに応じた堅牢なトランザクション・ソリューションを作成するために利用できる 4 つのトランザクション・ストラテジーを紹介しました。トランザクション処理は傍から見ると単純かもしれませんが、さまざまなビジネス・アプリケーション・シナリオに適用してみると、かなり複雑な処理になってくるはずです。私がこの連載で目標としたのは、その複雑さを軽減し、データの完全性と整合性を高いレベルで維持するという挑戦的と思われるタスクを単純化する手法とストラテジーを明らかにすることでした。トランザクションに関するこれまでの記事が、トランザクション処理の観点からアプリケーションとデータの堅牢性を向上させるために必要な知識を皆さんに提供できたことを願います。
参考文献 学ぶために
製品や技術を入手するために
- iBATIS: iBATIS は、ハイパフォーマンス・トランザクション・ストラテジーで使用できる優れたエンタープライズ規模の SQL マッピング・フレームワークです。
- Spring JDBC: ハイパフォーマンス・トランザクション・ストラテジーで使用する候補として、この JDBC ベースのフレームワークについて調べてください。
- Business Activity Framework: この JBoss フレームワークの詳細を学んでください。
議論するために
著者について  | 
|  | Mark Richards は、Collaborative Consulting, LLC のディレクター兼シニア・テクニカル・アーキテクトです。彼は『Java Message Service』(O'Reilly、2009年) の第 2 版、そして『Java Transaction Design Strategies』(C4Media Publishing、2006年) の第 2 版の著者であり、寄稿者としても『97 Things Every Software Architect Should Know』(O'Reilly、2009年)、『NFJS Anthology Volume 1』(Pragmatic Bookshelf、2006年)、『NFJS Anthology Volume 2』(Pragmatic Bookshelf、2007年) などの本に貢献しています。IBM、Sun、The Open Group、そして BEA の認定アーキテクトおよび技術者である彼は No Fluff Just Stuff Symposium Series ではお馴染みの講演者で、世界各地のその他のカンファレンスやユーザー・グループでも講演を行っています。 |
記事の評価
|