2007年5月7日月曜日

Transaction

Spring framework の TransactionManager を使用してみる。
(この部分に関しては初めてなので、内容は実験的なもので
実践的なものではありません。)

Hibernate で大量のデータを insert していくと非常に時間がかかる。
メモリ内に持つ場合に比べてオーダが2つか3つ異なる。出し方は
忘れたが、auto-commit モードでは遅いよ見たいな事を言われたことが
あったので、設定ファイルでプロパティとしてオフにしてみたり
SQLConnection/Statement を得てオフにしてみたりしたが効果がない。

ではいったいどうやって auto-commit をオフにするかを考えていた。
もちろん、全てを素の JDBC でやる気は毛頭ない。ぼんやりと思って
いたのは Spring の TransactionManager を使えばオフになるのでは
ということ。

1.X の文法はなんともわかりにくかった。2.0 の aop/tx の形式なら
何とかなるかもということでやってみる。


<aop:config>
<aop:advisor pointcut="execution(* *..XRecDaoImplTx.insertXRecord(..))"
advice-ref="txAdvice"/>
</aop:config>

<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="insertXRecord"
propagation="REQUIRED"
timeout="20"/>
</tx:attributes>
</tx:advice>


ところがこれだけではどうも効果がわからない。
aop, transaction のログレベルを DEBUG にすると動作がなんとなく
わかるようになる。


log4j.category.org.springframework.transaction=DEBUG
log4j.category.org.springframework.aop.config=DEBUG



[main] DEBUG (NameMatchTransactionAttributeSource.java:addTransactionalMethod) - Adding transactional method [insertXRecord] with attribute [PROPAGATION_REQUIRED,ISOLATION_DEFAULT,timeout_20]
[main] DEBUG (NameMatchTransactionAttributeSource.java:addTransactionalMethod) - Adding transactional method [xrec1Tx] with attribute [PROPAGATION_REQUIRED,ISOLATION_DEFAULT,timeout_20]
[main] DEBUG (TransactionSynchronizationManager.java:bindResource) - Bound value [org.springframework.jdbc.datasource.ConnectionHolder@146c2f2] for key [org.springframework.jdbc.datasource.DriverManagerDataSource@1d9a2ab] to thread [main]
[main] DEBUG (TransactionSynchronizationManager.java:initSynchronization) - Initializing transaction synchronization
[main] DEBUG (TransactionAspectSupport.java:prepareTransactionInfo) - Getting transaction for [ibatisinteg.dao.XRecDao.insertXRecord]
[main] DEBUG (TransactionSynchronizationManager.java:getResource) - Retrieved value [org.springframework.jdbc.datasource.ConnectionHolder@146c2f2] for key [org.springframework.jdbc.datasource.DriverManagerDataSource@1d9a2ab] bound to thread [main]
[main] DEBUG (TransactionSynchronizationManager.java:getResource) - Retrieved value [org.springframework.jdbc.datasource.ConnectionHolder@146c2f2] for key [org.springframework.jdbc.datasource.DriverManagerDataSource@1d9a2ab] bound to thread [main]
[main] DEBUG (TransactionAspectSupport.java:commitTransactionAfterReturning) - Completing transaction for [ibatisinteg.dao.XRecDao.insertXRecord]
[main] DEBUG (TransactionSynchronizationManager.java:clearSynchronization) - Clearing transaction synchronization
[main] DEBUG (TransactionSynchronizationManager.java:unbindResource) - Removed value [org.springframework.jdbc.datasource.ConnectionHolder@146c2f2] for key [org.springframework.jdbc.datasource.DriverManagerDataSource@1d9a2ab] from thread [main]
[main] DEBUG (TransactionSynchronizationManager.java:bindResource) - Bound value [org.springframework.jdbc.datasource.ConnectionHolder@178bb14] for key [org.springframework.jdbc.datasource.DriverManagerDataSource@1d9a2ab] to thread [main]
[main] DEBUG (TransactionSynchronizationManager.java:initSynchronization) - Initializing transaction synchronization
[main] DEBUG (TransactionAspectSupport.java:prepareTransactionInfo) - Getting transaction for [ibatisinteg.dao.XRecDao.insertXRecord]
[main] DEBUG (TransactionSynchronizationManager.java:getResource) - Retrieved value [org.springframework.jdbc.datasource.ConnectionHolder@178bb14] for key [org.springframework.jdbc.datasource.DriverManagerDataSource@1d9a2ab] bound to thread [main]
[main] DEBUG (TransactionSynchronizationManager.java:getResource) - Retrieved value [org.springframework.jdbc.datasource.ConnectionHolder@178bb14] for key [org.springframework.jdbc.datasource.DriverManagerDataSource@1d9a2ab] bound to thread [main]
[main] DEBUG (TransactionAspectSupport.java:commitTransactionAfterReturning) - Completing transaction for [ibatisinteg.dao.XRecDao.insertXRecord]
[main] DEBUG (TransactionSynchronizationManager.java:clearSynchronization) - Clearing transaction synchronization
[main] DEBUG (TransactionSynchronizationManager.java:unbindResource) - Removed value [org.springframework.jdbc.datasource.ConnectionHolder@178bb14] for key [org.springframework.jdbc.datasource.DriverManagerDataSource@1d9a2ab] from thread [main]


よく見ると Getting Transaction for [method name] と Completing transaction for [method name]
が繰り返し出てきている。実際に数えると insert と同じ回数出てきている。

これから、ポイントカットに入るときに開始して、出るときに終了しているらしいことが
わかる。これでは元のつくりより遅いではないか。

では大量の insert を行う処理を呼ぶ側につけてはどうか。

これをやっているときにも少しつまづいた。ポイントカットに指定するメソッドは
インターフェイスで定義されたものの実装でなければならないようである。
当然、static メソッドは使えない。継承関係が複雑な場合(あるインターフェイスを
実装した抽象クラスのサブクラスが他のインターフェイスを実装していてといった
感じ) でもうまく動かなかった。

試行錯誤の後、この方法でトランザクション中はコミットが入らずデータの追加が
早くできた。失敗した場合のことはまだ考えていない。エラーが起きないように
データを用意するか、それともエラーが起きたときにデータをチェックして正しい
データのみ登録するか、それぞれ面倒そうなのでじっくり考えることにしよう。

最初に設定したデータベース処理に近いポイントカットも enable してみると
パフォーマンスの低下は見られなかった。どうやらネストしたトランザクション
スコープからぬける際はフラッシュはかからないようだ。

ibatis のバッチモードも大量データ追加に効果があるようなことが書かれていたので
試してみたが、いまひとつ。むしろ TransactionManager が効果的のようだ。
(試したサンプルは batch mode を transaction 開始、終了で囲んであったので
batch だけやっても意味はないのかも)

Hibernate のパフォーマンスだが、最終的にはトランザクションを使わない場合の
2, 3 倍程度となった。(Embedded Derby の場合) これは少し期待はずれ。
MySQL でやると改善率は高いのだが、もともとがとても遅いわけでやはりこれも
使えない。

Hibernate をつかったプログラムでは自前でバッチ更新するコードを入れていた
(Hibernate template に Collection をわたす)。この場合、ひとつのセッションの
中で更新・追加が行われるわけで TransactionManager を使わなくとも早く
できることがわかった。ただ、エラーハンドリングをどうするかは依然として
課題である。ibatis のバッチモードもこのようなことなのかも知れない。

かなり入り口の部分でポイントカットを設定して走らせて見ると OutOfMemoryError が
でた。commit するまではどこかにためておかれるみたい。定期的にフラッシュする
簡単な方法があると良いのだが。トランザクションの本来の使い方から大きく
逸脱しているので難しそうだが...

結局、大量のデータ登録を行うには DB 固有の import がもっとも速そう。しかし、
これもいろいろな制約のついたテーブル構造・関係の下ではいろいろなエラーが
おきうるので頭が痛い...

0 件のコメント: