IBM®
本文へジャンプ
    Japan [変更]    ご利用条件
 
 
検索範囲検索:    
    ホーム    製品    サービス & ソリューション    サポート & ダウンロード    マイアカウント    
skip to main content

developerWorks Japan  >  Java technology  >

Javaの理論と実践: JTSを理解する -- 安全とパフォーマンスのバランス

トランザクションの境界設定と独立性のガイドライン

developerWorks
ページオプション

JavaScript を要するドキュメントオプションは表示されません

原文はこちら

原文はこちら


レベル: 初級

Brian Goetz (brian@quiotix.com), Principal Consultant, Quiotix

2002年 5月 01日

JTSに関するBrian Goetz氏のシリーズの第1回と第2回で、氏は、トランザクションとは何か、またJ2EEコンテナーがどのようにEJBコンポーネントに意識させずにトランザクション・サービスを行うのか、という点について基本となるポイントを説明しました。コンポーネントのトランザクション・セマンティクスを、プログラム処理ロジックではなく、宣言的に指定できることによって、企業用アプリケーションの構成に際して優れた柔軟性が提供されますが、アプリケーションの組み立て時に適切な決定を行わないと、アプリケーションのパフォーマンスと安定性を低下させることになります。このシリーズの最後となる今回の記事で、Goetz氏は、J2EEがトランザクションの境界設定と独立性を管理するために提供する機能と、それらを効果的に使用するためのガイドラインについて取り上げています。

このシリーズの第1回「トランザクションについて」および第2回「見えない魔法」では、トランザクションとは何かを定義し、トランザクションの基本的なプロパティーを列挙し、またJava Transaction ServiceとJ2EEコンテナーがどのように協力してJ2EEコンポーネントに意識させずにトランザクションのサポートを提供するのかについて説明しました。今回の記事では、トランザクションの境界設定と独立性について取り上げます。

EJBコンポーネントのトランザクションの境界設定と独立性に関する属性を定義するのは、アプリケーション組立担当者です。これらを正しく設定しないと、アプリケーションのパフォーマンスやスケーラビリティー、耐障害性に深刻な結果をもたらします。残念なことに、これらの属性を正しく設定するための明確なルールはありません。しかし、並行性の問題とパフォーマンスの問題とのバランスを見つけるためのガイドラインはあります。

第1回で説明したように、トランザクションは主として例外処理のメカニズムです。トランザクションは、法律に基づく契約が日常のビジネスで担っているのと同じような目的をプログラム内で担っています。何か問題が起こった場合には、それらが回復を手助けしてくれます。しかし実際は、ほとんどの場合問題など何も起こらないので、我々はそれ以外の場合のコストや手間を最小限に抑えられるようにしたいと考えます。アプリケーションにおいてどのようにトランザクションを使用するかということが、アプリケーションのパフォーマンスとスケーラビリティーに大きな影響を与える場合があります。

トランザクションの境界設定

J2EEコンテナーは、トランザクションの開始と終了を定義するために、Bean管理トランザクションとコンテナー管理トランザクションという2つのメカニズムを提供します。Bean管理トランザクションでは、UserTransaction.begin()UserTransaction.commit() によって、Beanメソッドで明示的にトランザクションを開始し、終了します。一方、コンテナー管理トランザクションは、はるかに優れた柔軟性を提供します。アセンブリー記述子で各EJBメソッドのトランザクション属性を定義することによって、各メソッドのトランザクションの要件を指定し、トランザクションの開始と終了をコンテナーに決定させることができます。どちらの場合も、トランザクションを構造化する基本的なガイドラインは同じです。

中に入れ、外に出す

トランザクションの境界設定の第1のルールは、「短くしておくこと」です。トランザクションは並行性制御 (concurrency control) を提供します。一般に並行性制御とは、トランザクションの間にアクセスするデータ項目のロックをリソース・マネージャーが取得し、トランザクションが終了するまで保持することを意味します。(このシリーズの第1回ではACID プロパティーについて説明しました。「ACID」の「I」が「Isolation (独立性)」を表していたことを思い出してください。つまり、あるトランザクションが、並行して実行されている他のトランザクションに影響を与えないということです。) ロックが保持されている間は、ロックされているデータ項目にアクセスが必要な他のどのトランザクションも、ロックが解除されるまで待たなければなりません。トランザクションが非常に長い場合は、他のそのようなトランザクションはすべてブロックされ、アプリケーションのスループットが急速に低下します。

ルール1: トランザクションはできるだけ短くしておくこと。

トランザクションを短くしておくことによって、他のトランザクションをブロックする時間を最小限に抑え、それによってアプリケーションのスケーラビリティーが向上します。トランザクションをできるだけ短くしておく最良の方法は、言うまでもなく、トランザクションの途中で不必要に時間を費やすようなことを一切しないようにすることです。特に、トランザクションの途中でユーザー入力を待つようなことをしてはいけません。

トランザクションを開始し、データベースからデータを検索し、そのデータを表示し、そしてまだトランザクションの最中だというのにユーザーに選択を迫りたくなるかもしれません。しかし、そのようなことをしてはいけません。たとえユーザーが注意していても、応答にはやはり数秒かかります。これは、データベースのロックを保持する時間としては長い時間です。さらにユーザーが昼食をとるため、あるいはその日はもう帰宅してしまうために、コンピューターから離れる場合はどうなるでしょうか。アプリケーションは、ただ停止しているだけです。トランザクションの処理中にI/Oを行うことは、不幸な結果を招く原因になります。

ルール2: トランザクションの処理中に、ユーザー入力を待たないこと。

関連したオペレーションをまとめる

各トランザクションが小さいとはいえないオーバーヘッドを引き起こすので、1オペレーション当たりのオーバーヘッドを最小限に抑えるために、1つのトランザクションでできるだけ多くのオペレーションを行うのが最もよい方法だと思われるかもしれません。しかしルール1で、長いトランザクションはスケーラビリティーに悪影響を及ぼすことがわかりました。では、オペレーションごとのオーバーヘッドを最小限に抑えることとスケーラビリティーとのバランスをどのように調整すればよいのでしょうか。

ルール1を論理的な極論でとらえると、つまり1つのトランザクションには1つのオペレーションと考えてしまうと、追加のオーバーヘッドがかかるだけでなく、アプリケーションの状態の一貫性も危うくなります。トランザクション・リソース・マネージャーは、アプリケーションの状態の一貫性を維持するものですが (第1回では「ACID」の「C」が「Consistency (一貫性)」を表していたことを思い出してください)、一貫性とは何を意味するのかという定義についてはアプリケーションに依存しています。実際に、トランザクションを記述する際に使用した一貫性の定義は循環論的です。一貫性とは、アプリケーションがそうであると示したものをすべて意味します。アプリケーションは、アプリケーションの状態に対する変更のグループをトランザクションにまとめます。その結果、アプリケーションの状態はその定義するところの通り一貫します。そしてリソース・マネージャーは、それが障害から回復しなければならない場合に、アプリケーションの状態を最も最近の一貫した状態に復元します。

第1回では、バンキング・アプリケーションにおいてある口座から別の口座へ振替を行う例を示しました。リスト1はSQLでのこの実装例を示しています。この実装には5つのSQLオペレーション (1つの選択、2つの更新、および2つの挿入) が含まれています。



リスト1. 振替のためのSQLコードの例
                
SELECT accountBalance INTO aBalance 
    FROM Accounts WHERE accountId=aId;
IF (aBalance >= transferAmount) THEN 
    UPDATE Accounts 
        SET accountBalance = accountBalance - transferAmount
        WHERE accountId = aId;
    UPDATE Accounts 
        SET accountBalance = accountBalance + transferAmount
        WHERE accountId = bId;
    INSERT INTO AccountJournal (accountId, amount)
        VALUES (aId, -transferAmount);
    INSERT INTO AccountJournal (accountId, amount)
        VALUES (bId, transferAmount);
ELSE
    FAIL "Insufficient funds in account";
END IF

このオペレーションを5つのトランザクションとして別々に実行すると、どうなるでしょうか。(トランザクションのオーバーヘッドが原因で) さらに遅くなるだけでなく、一貫性も損なわれます。たとえば、最初のSELECT (残高の確認) と次の借方のUPDATEの実行の間に、別のトランザクションの一部として口座Aからお金を引き出すとどうなるでしょうか。これは、このコードによって実行されることになっているビジネス・ルール (口座残高をマイナスにしない) に違反します。また、最初のUPDATEと2番目のUPDATEの間にシステム障害が発生した場合はどうでしょうか。システムが回復した時点で、口座Aから出金したものの、口座Bには入金していないでしょう。そして、その理由を示す記録も残りません。これでは、どちらの口座の所有者も満足しないでしょう。

リスト1の5つのSQLオペレーションは、ある口座から別の口座へお金を振り替えるという、関連した1つのオペレーションの一部です。したがって、すべてが実行されるか、あるいは何も実行されないかのどちらかであってほしいわけで、1つのトランザクションですべてが行われるべきなのです。

ルール3: 関連したオペレーションを1つのトランザクションにまとめる。

理想的なバランス

ルール1では、トランザクションをできるだけ短くすべきだと指摘しました。一貫性を維持するには、オペレーションを1つのトランザクションにまとめなければならない場合もある、ということをリスト1の例は示しています。もちろん、何が「関連したオペレーション」となるかという判断は、アプリケーションによって異なります。トランザクションの範囲を記述するための一般的なガイドラインを示すために、ルール1とルール3を組み合わせることができます。それがルール4です。

ルール4: 関連したオペレーションは1つのトランザクションにまとめ、関連性のないオペレーションは別々のトランザクションにする。



上に戻る


コンテナー管理トランザクション

トランザクションの開始と終了を明示するかわりに、コンテナー管理トランザクションを使用する場合、各EJBメソッドのトランザクション要件を定義します。トランザクション・モードは、Beanのassembly-descriptorcontainer-transaction セクションのtrans-attribute 要素で定義されます。(assembly-descriptor の例は、リスト2に示してあります。) メソッドのトランザクション・モードと、呼び出しメソッドが既にトランザクションの中で登録されているかどうかを示す状態は、EJBメソッドが呼び出された場合に、コンテナーが複数のアクションの中からどのアクションを実行するかを決定します。

  • 既存のトランザクションの中にメソッドを登録する。
  • 新しいトランザクションを作成し、その中にメソッドを登録する。
  • メソッドをどのトランザクションの中にも登録しない。
  • 例外をスローする。


リスト2. EJBアセンブリー記述子 の例
                
<assembly-descriptor>
  ...
  <container-transaction>
    <method>
      <ejb-name>MyBean</ejb-name>
      <method-name>*</method-name>
    </method>
    <trans-attribute>Required</trans-attribute>
  </container-transaction>
  <container-transaction>
    <method>
      <ejb-name>MyBean</ejb-name>
      <method-name>logError</method-name>
    </method>
    <trans-attribute>RequiresNew</trans-attribute>
  </container-transaction>
  ...
</assembly-descriptor>

J2EE仕様は、6つのトランザクション・モード (RequiredRequiresNewMandatorySupportsNotSupported、およびNever ) を定義しています。表1は、既存のトランザクションで呼び出された場合とトランザクション以外で呼び出された場合の各モードの振る舞いをまとめ、さらにどのタイプのEJBコンポーネントが各モードをサポートするのかを示しています。(トランザクション・モードを選択する際に、より優れた柔軟性を持つコンテナーもありますが、その使用はコンテナー固有の機能に依存しているため、どのコンテナーでも常に使用できるというわけではありません)。


表1. トランザクション・モード
トランザクション・モードBeanタイプトランザクションTで呼び出された場合のアクショントランザクション外で呼び出された場合のアクション
Required Session、Entity、Message-drivenTに登録新しいトランザクション
RequiresNew Session、Entity新しいトランザクション新しいトランザクション
Supports Session、Message-drivenTに登録トランザクションなしで実行
Mandatory Session、EntityTに登録エラー
NotSupported Session、Message-drivenトランザクションなしで実行トランザクションなしで実行
Never Session、Message-drivenエラートランザクションなしで実行

コンテナー管理トランザクションのみを使用するアプリケーションでは、トランザクション・モードがRequired またはRequiresNew であるEJBメソッドをコンポーネントが呼び出した場合にのみ、トランザクションが開始されます。コンテナーがトランザクション・メソッドを呼び出した結果としてトランザクションを作成すると、そのトランザクションはメソッドが完了したときに終了します。メソッドが正常に戻された場合、コンテナーはトランザクションをコミットします (アプリケーションがトランザクションにロールバックを要求しなかった場合)。メソッドが例外をスローすることによって終了した場合、コンテナーはトランザクションをロールバックし、例外を伝播します。メソッドが既存のトランザクションTで呼び出され、トランザクション・モードが、メソッドをトランザクションなしで実行するか、または新しいトランザクションで実行すると指定した場合、トランザクションTはメソッドが完了するまで中断され、その後、直前のトランザクションTが再開されます。

トランザクション・モードの選択

では、Beanメソッドにはどのモードを選ぶとよいでしょうか。Session BeanおよびMessage-driven Beanの場合は、各呼び出しが1つのトランザクションの一部として実行され、かつメソッドがより大きなトランザクションの1つのコンポーネントにもなれるように、通常はRequired を使用することができます。RequiresNew を使用する場合は注意してください。これは、メソッドのアクションが、呼び出されたメソッドのアクションとは別にコミットできることがわかっている場合にのみ使用してください。一般にRequiresNew は、常にロギング・オブジェクトなど、システムの他のオブジェクトにはほとんど関係ないか、またはまったく関係ないオブジェクトと共に使用します。(エンクロージング・トランザクションのコミットに関係なくログ・メッセージをコミットすることができるので、RequiresNew をロギング・オブジェクトと共に使用するのは当然のことです。)

リスト1では、コードを1つのトランザクションではなく5つのトランザクションとして別々に実行すると、アプリケーションの状態に一貫性がなくなりましたが、RequiresNew を不適切な方法で使用すると、これと同じような結果になる可能性があります。

CMP (container-managed persistence、コンテナー管理パーシスタンス) Entity Beanには、通常はRequired を使用することができます。特に初期の開発の場合は、Mandatory を選ぶのもよいでしょう。これは、Entity Beanメソッドがトランザクション外で呼び出されている場合に警告を与えてくれます。これによって配備エラーがわかる場合があります。CMP Entity BeanにはRequiresNew は使用することはないと言って差し支えないでしょう。NotSupportedNever は、(外部の非トランザクション・システム用のアダプターなどの)非トランザクション・リソース、あるいは (Java Transaction API (JTA) トランザクションでは登録できない) トランザクション・システムを対象としています。

EJBアプリケーションが正しく設計されると、トランザクション・モードに関する上記ガイドラインを適用することによって、自然に、ルール4で提示されたトランザクション境界の設定になります。その理由は、J2EEアーキテクチャーが、アプリケーションを最小の便利な処理の塊に分解し、各塊が個別の要求として処理されるからです (HTTP要求の形でも、JMSキューに並んでいるメッセージの結果としても)。




上に戻る


再び、独立性について

第1回で、独立性とは、あるトランザクションの影響が、並行して実行されている他のトランザクションから見えないことであると定義しました。トランザクションの観点から見ると、トランザクションは、並列にではなく順次に実行されているように見えます。トランザクション・リソース・マネージャーは、多くの場合、独立性があるように見せかけながら多くのトランザクションを同時に処理することができますが、実際には独立性の制約により、既存のトランザクションが完了するまで新しいトランザクションの開始が延期される場合があります。トランザクションの完了には、少なくとも1つの同期ディスクI/O (トランザクション・ログへの書き込み) が伴うため、1秒当たりのトランザクションの数が、1秒当たりのディスク書き込み数程度に制限され、スケーラビリティーに悪影響を与えます。

実際には、より多くのトランザクションを並行して実行できるようにし、システム・レスポンスおよびスケーラビリティーを改善できるようにするため、独立性の要件を大幅に緩和するのが一般的です。ほぼすべてのデータベースが、4つの標準の独立性レベル (Read Uncommitted、Read Committed、Repeatable Read、およびSerializable) をサポートしています。

残念なことに、コンテナー管理トランザクションの独立性の管理は、現在はJ2EE仕様の範囲内ではありません。しかしIBM WebSphereやBEA WebLogicなどの多くのJ2EEコンテナーが、コンテナー固有の拡張を提供し、トランザクション・モードを アセンブリー記述子で設定するのと同じ方法で、メソッドごとにトランザクションの独立性レベルを設定することができます。Bean管理トランザクションは、JDBCまたは他のリソース・マネージャー接続を介して独立性レベルを設定することができます。

独立性レベル間の相違を示すために、まず、並行性に関するいくつかの問題 -- 適切な独立性がない場合に、あるトランザクションが別のトランザクションを干渉するケース -- を分類してみましょう。以下の問題はすべて、あるトランザクションが、2番目のトランザクションが既に開始された後で、その2番目のトランザクションから見えるようになるという結果に関係しているはずです。

  • Dirty Read: トランザクションの中間 (非コミット) 結果が別のトランザクションから見える場合に発生。
  • Unrepeatable Read: トランザクションがデータ項目を読み込み、次に同じ項目を再び読み込んだときに、異なる値が出た場合に発生。
  • Phantom Read: トランザクションが複数の行を戻すクエリーを実行し、その後、同じクエリーを再び実行し、そのクエリーが最初に実行されたときになかった行が追加された場合に発生。

4つの標準の独立性レベルは、表2に示されているように、これら3つの独立性の問題に関係があります。最も低い独立性レベルのRead Uncommittedは、他のトランザクションによって行われた変更を保護しませんが、読み取りロックの競合が必要ないため、最も高速です。最も高い独立性レベルのSerializableは、上述の独立性の定義と同等で、各トランザクションは、他のトランザクションの影響から完全に分離されているように見えます。


表2. トランザクションの独立性レベル
独立性レベルDirty readUnrepeatable readPhantom read
Read Uncommittedありありあり
Read Committedなしありあり
Repeatable Readなしなしあり
Serializableなしなしなし

ほとんどのデータベースに関して、デフォルトの独立性レベルはRead Committedであり、これは適切な選択です。というのも、それによって、トランザクションでは、トランザクションのある時点でアプリケーション・データの一貫性が損なわれることがないからです。Read Committedは、レポート用のデータやユーザーに表示するデータをフェッチしたり (おそらくWeb要求の結果として)、新しいデータをデータベースに挿入するなど、大半の典型的な短いトランザクションに使用される適切な独立性レベルです。

さらに高い独立性レベルのRepeatable ReadおよびSerializableは、トランザクション全体でより高度な一貫性が必要な場合に適しています。たとえばリスト1の例のように、口座残高が十分にあることを確認してから、実際に口座から引き出すまで、口座残高が変わらないようにするには、少なくともRepeatable Readの独立性レベルが必要です。口座のすべての借方と貸方の合計が現在の残高に等しいことを確認するための会計データベースの監査など、データの一貫性が不可欠な場合は、作成中の新しい行に対する保護も必要です。そのような場合に、Serializableの使用が必要となります。

最も低い独立性レベルのRead Uncommittedはほとんど使用されません。これは、近似値さえ得られればよいような場合に適しています。さもないと、問合せに望ましくないパフォーマンスのオーバーヘッドがかかるような場合です。Read Uncommittedを使用する典型的な例は、その日に発注された数量や総金額など、急速に変化する量を見積もる場合です。

独立性とスケーラビリティーの間には実質的なトレードオフの関係があるので、トランザクションの独立性レベルの選択には注意が必要です。レベルが低すぎると、データを損なう可能性があり、またレベルが高すぎると、ロードが軽い場合は別ですが、パフォーマンスに悪影響を与えるおそれがあります。通常、データの一貫性の問題は、パフォーマンスの問題よりも重要です。不安がある場合は、用心するに越したことはないので、より高い独立性レベルを選んでください。そして、それがルール5につながります。

ルール5: データの安全性を確保できる最も低い独立性レベルを使用すること。しかし不安がある場合は、Serializableを使用すること。

用心するに越したことはないという考えから最初は高い独立性レベルを採用しておき、結果的にパフォーマンスが許容できるレベルに達してくれることを祈るやり方 (これは、「否認と祈り」と呼ばれるパフォーマンス管理のテクニックです。ほとんどの開発者は認めないでしょうが、おそらく最も一般的に採用されているパフォーマンス戦略です)を採用するつもりであっても、コンポーネンを進めていく中で独立性の要件について検討することは意味のあることです。パフォーマンスが問題になった場合に、自らを窮地に追い込むことにならないよう、実践の場で、より低い独立性レベルに耐えられるトランザクションを記述するようにしてください。独立性レベルを正しく設定するには、メソッドが何を行い、どのような一貫性の前提がそこに埋め込まれているかを知ることが必要なので、開発の間に並行性の要件と前提を注意深く文書化するのもよい考えです。そうすれば、アプリケーションの組み立て時に正しい判断を行うことができます。




上に戻る


結論

この記事で提案したガイドラインの多くは、いくらか矛盾しているように見えます。というのも、トランザクションの境界設定や独立性などの問題は本質的にトレードオフの関係にあるからです。我々が目指しているのは、最低限の安全を得るために、安全と、使用するツールのパフォーマンス・オーバーヘッドとのバランスをうまく調整するということです(もし安全に留意しないのなら、トランザクションについて頭を悩ますことはまったくないでしょう)。正しいバランスは、システムの障害またはダウン時間に関連するコストや損害、および組織的なリスク許容度など、多くの要因によって異なるでしょう。



参考文献



著者について

Brian Goetz は18 年間以上に渡って、専門的ソフトウェア開発者として働いています。彼はカリフォルニア州ロスアルトスにあるソフトウェア開発コンサルティング会社、Quiotixの主席コンサルタントであり、またいくつかのJCP Expert Groupの一員でもあります。2005年の末にはAddison-Wesleyから、Brianによる著、Java Concurrency In Practiceが出版される予定です。Brian著による有力業界紙に掲載済みおよび掲載予定の記事のリストを参照してください。




記事の評価


サイト改善のため、ご意見をお寄せください。こちらのフォームからお願いいたします。



 


 


不充分・不完全である大変素晴らしい
 


この記事を共有する

del.icio.us del.icio.us newsing newsing FC2ブックマーク FC2ブックマーク
Choix! Choix! ニフティクリップ ニフティクリップ Yahoo!ブックマーク Yahoo!ブックマーク
MM/memo MM/memo CZブックマーク CZブックマーク livedoorクリップ livedoorクリップ
はてなブックマーク はてなブックマーク Buzzurl(バザール) Buzzurl(バザール)




上に戻る


    日本IBMについて プライバシー お問い合わせ