トップ «前の日記(2004-10-01) 最新 次の日記(2004-10-03)» 編集

日々の破片

Subscribe with livedoor Reader
著作一覧

2004-10-02

_ デッドロック

コンポーネントAからコンポーネントEまでが連携してa〜cまでのテーブルを処理する場合、これまでの慣習から各コンポーネントと更新するテーブルの関係がA(a)-B(b)-C(a)-D(b)-E(c)だとする。

ちょっと待った。これってデッドロックするんじゃないか?

と普通は考えるだろう。(同時に複数のトランザクションが実行される場合)

たとえば

A(a-0)-B(b-1)-C(a-1)-D(b-2)-E(c-0)

A(a-1)-B(b-0)-C(a-2)-D(b-1)-E(c-0)

を同時に実行すれば、ほぼ確実にCかDで止まる。

この場合、AからEまでで1つのトランザクションなのだから、

A(a)-C(a)-B(b)-D(b)-E(c)

に変えることができる。し、そうでなければそれは1つのトランザクションではない。(実際はBがCの実行条件を設定するかも知れないので、そうとは言えないが、この時点ではその可能性は無いと仮定する)

ところが、これまた過去の経緯で処理すべきキーが必ずしも昇番順でないとする。

たとえば

A(a-1)-C(a-0)...

A(a-0)-C(a-1)...

を同時に実行すればこれまた確実に止まる。

この場合、動的にコンポーネントの実行順序を変えてみたらどうだろうか。

つまり、

C(a-0)-A(a-1)...

A(a-0)-C(a-1)...

というように、対象となるテーブルのキーの昇番順に処理順を変えるということだ。

このように変更すればデッドロックは起きない。

しかし、動的に実行順序を変えるということが可能だとして、本当にそれで処理的な問題は発生しないのだろうか?

それはもちろん処理内容に依存する。AでCが利用するなんらかの値や状態をセットアップするのであれば、この順序の変更は致命的だ。

そこでAで実行するセットアップ処理A'をAから分離してみる。この場合、

A'-C(a-0)-A(a-1)...

A'-A(a-0)-C(a-1)...

と実行することになる。しかし、A'の処理内容がAの結果に依存するのであれば、すなわち、A'の実行条件として事前にAの処理が完了していなければならないとしたら、この方法は単なる画へ居(むかむか、なんで変換しないんだ。さらしとく――追記:コメント欄参照)と化す。で、たいていの場合、そんなものだ。

また、Cで処理するロウの決定がAの処理に依存する場合には、最初から順序を変えることはできない。

結局、おもしろそうではあるが、実行時に順序を変更するという方法はうまくいかないことになる。

そこで、あらかじめ、デッドロックが発生することを前提してしまうという方法も取れる。楽観的に行こう。どうせデッドロックすればデータベースマネージャが教えてくれる。

だが、タイムアウトを待たせるのは問題があるとする。レスポンスが重要な場合。

その時は、処理表みたいなものをグローバルに参照可能として、自分がロックすべきロウをすでにロックしたトランザクションが、現在自分がロックしたロウに対する待ち状態になっているか判断させるという制御をすることになる。

これは面倒だ。

そこで、やはりデッドロックの発生をロジカルに無くす方法を考えることにする。

それは可能か? まず、Aを実行しなければCの処理対象が決定できないというルールをどうにかしなければならない。たとえば、ここで1回トランザクションを完結させればどうだろうか? だが、Eの実行に失敗したらすべて廃棄という処理であれば、ここで完結させるのは考えものだ。

しかし、補償ができるのであればこの一連の処理をロングトランザクションとみなしてしまえば良いことになる。

とすれば、たかだか3個のテーブルを更新する5つの処理であっても、ロングトランザクション足りえる要件を持っているということになる(ロングトランザクションという呼び名よりは、サーガトランザクションという呼び名のほうが、この場合しっくりする)。

だが、それ以前の問題としてアイソレーションの必要性があればどうか? A-B-C-D-Eが完結するまで更新内容を他のトランザクションが参照してはならない場合だ。

そこで面倒になって、シリアライザブルに実行させることにしようと考えてみる。しかし、デッドロックの現実的な可能性と、実際に捌かなければならないトランザクション量、AからEまでの処理時間を予測すると、どうしても並列処理が必要だとする。

結局、どうにもならないのではないか?

そこで、ぎりぎりの短さでデッドロックタイムアウト検出時間を設定し、再試行処理を実装することを考える。

実はこれが一番、簡単である。A-B-C-D-Eを順に呼び出すコントローラがデッドロックでロールバックしたことを検出できれば良いからだ。

for (;;) {

try {

A(); B(); C(); D(); E();

} catch (DeadLockDetected e) {

continue;

} catch (Exception e) {

log("!");

}

break;

}

しかし、もっとうまい方法があるような気がする。そもそもAとCが同一のテーブルaを更新するというのが間違いなのではないか? 正規化すれば実際には異なるテーブルとなるものを、1つのテーブルとしてインスタンス化してしまったのではないか?

あるいは、本当はA-B-Eと、C-D-Eという異なるトランザクションなのではないか? もしそうならば、Aの処理完了時に同一トランザクションを利用するキューイングでC-D-Eの実行をスケジュールすれば良い。だが、そうではなかったらどうすれば良いか。

デッドロックする可能性があるトランザクションの存在というのは分析ミスなのか、それとも仕様に内在されるものなのか?

もし後者だとしたら、何かおかしいのではないだろうか。あらかじめエラーがロジカルに予見されるシステムというのが、なぜあり得るのか。

_ 混迷の現代(どういう意味だ?)

思想を持てない人は、他人が「思想」と口に出すと勝手に痛みを感じキレる。存在の空虚さを無意識のうちに自覚しているからだ。似たような症状として、他人の言質に対しプロ市民というおそらく誤用に基づくレッテル貼りをすることとして顕現することも散見される。自分が選択したアティテュードの歴史的な正当性の無さを無意識のうちに自覚しているからだ。

#と空疎にして根拠がない言葉を断定調で書いてみたり。

本日のツッコミ(全2件) [ツッコミを入れる]
_ かくたに (2004-10-02 22:09)

「がべい」でも変換されないですか?

_ arton (2004-10-02 23:38)

さらしたのは、おいらの無知だったというオチだったのですね。


2003|06|07|08|09|10|11|12|
2004|01|02|03|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|09|10|11|12|
2019|01|02|03|04|05|06|07|08|09|10|

ジェズイットを見習え