日曜日, 5月 11, 2008
【EC開発体験記-トランザクション処理-】古くて新しい永遠のテーマ
トランザクション処理といえば、システムの基本中の基本で古くて新しいテーマである。ミッションクリティカルな基幹システムでは特に重要な要素だと言われて久しい。何で重要なのかはさておき、IBMの新人研修でもそう教えられるので、それが「常識」であることは間違いない。では、トランザクション処理って何のためにあるのか、結論を一言でいうと正しい情報を管理するためである。コミットで正しい情報、ロールバックで正しくないか、まだ受け付けられていない情報となる。長年IBMにいたせいか、ミッションクリティカルな基幹システムはメインフレームが最適という言葉が身に染み付いていて離れない(=洗脳されている)。最初はIBMがいうところの「真のミッションクリティカルな基幹システム」を研究したかった。実際に92年から94年までの担当はメインフレーム一色で、OS390、CICSやIMS、他社メインフレームまでいじっていた。(そういう私が今はLAMPで基幹システムをやるなんて・・思えばかなり冒険したものだと思う。)その時代はホストでもオープン化という大きな波があり、OS390がUNIX APIを搭載しはじめた頃で、新人ではあるものの、元々オープン系を得意としていた私にもいろいろと仕事はあった。MVSでHTTPサーバってどうやって動かすの?あのOSの構造を知っているものには想像すらつかない。実際、他社メインフレームのDBとOS390IHS(HTTPサーバ)を接続したときは自分でも感動したものだ。(この経験がY社での荷物追跡システムに生かされることになるのだが・・)
トランザクション処理はこの頃勉強した。commit/rollbackするまでの間においてACIDを保障する仕組みぐらいしか理解してなかったが、実際、DBやMQなどのサブシステムにはそう説明されているので、仕組みとしてはそれ以上でも以下でもない。しかし本当はもっと奥が深い。例えば、ロックを前提とするものが多いのでパフォーマンスに大きく影響する。また、アプリケーションが複雑になるとトランザクション開始点を覚えておかないといけないので管理が大変になる。実際に運用するとわかるのだが、厳密に管理できてないアプリはすぐにlockwaitで糞づまる。そのlockwaitの原因を紐解くと、トランザクション処理のバグであることが多い。しかし、すぐにはバグを認めたがらない開発者もいるので要注意だ。lockwaitというエラーメッセージの意味だけを説明して「DBサーバの増強が必要です」なんてヌケヌケといってくることもある。(私は直感的にバグと分かるのでカッときてバトルになることが多い)とはいえ、その「lockwaitの原因を紐解く」作業は結構大変だったりする。なぜかというと、実際にエラーメッセージを出している(lockwaitを引き起こしている)処理は無実であり、それより前にロックしている処理の方が有罪であることが多い。しかし、これが簡単には見つからないのである。また、これは私たちのアプリ特有のエラーではあるが、Duplicateエラーも同様に発見が困難である。これは履歴テーブルに受注レコードを移動させる際にキー重複が原因で起こる。移動処理では、受注テーブルにあるレコードを履歴テーブルにINSERTして元の受注テーブルからDELETEする。これが一つのトランザクション処理となっていれば問題ないのだが、エラーとなったトランザクションが正しくrollbackされていないために、残骸となって履歴テーブルに残ってしまっている。次に正しいトランザクションにより移動処理が実行されようとしても、この残骸のためにDuplicateエラーとなってしまっているのだ。結局、これらを解決するために、ソースを全部引っ張り出して「抜け」を探すことになる。「抜け」とは、開始点だけあるような処理、例えば、SELECT FOR UPDATEだけ実行されていてcommmit/rollbackされてない箇所のこと。他人のソースをひっくり返してロジックを追う作業は本当につらい。不具合を見つけたら見つけたで、よく説明しないと開発者との関係がギクシャクする。彼らはバグではないことを主張していたので自分が理解できるまで納得がいかない。そこでトランザクション処理の基本を講義したり、なぜlockwaitになるかをサンプルプログラムでシミュレーションするハメになる。パフォーマンス劣化の原因まで説明するのは本当に難しい。・・・ああ、面倒くさい、もう理解しなくていい。全部AutoCommitでいいよ。となって前記事に載せた、否定の1項目となる。
「5.トランザクション処理は厳密にしない」
これはこれで問題なのは明白だ。いいたいことは要するに、開発者にトランザクション処理を意識させない、にもかかわらず、厳密にトランザクション処理がなされること。AutoCommitのノリで全部書けちゃうのが理想だ。
思いかえすと、メインフレームやDB、Java(JDBC)で使われてきた、commit/rollbackに代表されるコード埋め込み型のトランザクション管理は元々問題あるとされてきた。それだから、EJBのトランザクション(コンテナによるコンテキスト化)に触れたときはショックだった。EJBは結局、コンテナが重くて使い物にならなかったが、Seasarはいけると思った。トランザクションコンテキストをAOPで持ちまわるなんて、なかなか素晴らしいアイデアである。これをJ2EE仕様を損なわずに実現している。しかしまだ不十分な感は否めない。
繰り返しになるが、開発者にトランザクションを意識させている点は大きな課題だ。それがJ2EE仕様を外れたとしても、意識させないことの方が重要だと思う。
一つの解として、すべてをサービス化して、その入り口と出口にトランザクションポイントを置く方法がある。トランザクションスクリプトというデザインパターンであるが、要は、サービスの実行が全部成功すれば、commitとし失敗すればrollbackとする。サービスの最小単位がAtomicとなるのは異論はあるかもしれないが、現時点では、これが一番分かりやすいパターンのような気がする。というわけで、Reflexはトランザクションスクリプトパターンを採用する。サービスの実装はトランザクションを意識せず自由に書いてもらって結構。ただし、エラー時はExceptionを親にスローすること。これだけですべて解決!・・・とうまくいくのかなあ。
登録:
コメントの投稿 (Atom)
0 件のコメント:
コメントを投稿