« 蒼星配列を取り込んだACT配列での句読点 | Main | 理想のマウス? »

Thursday, July 14, 2005

CGIのファイルロック問題

なるちゃんの日記より

実は密かに、CANO-LabでCGIやDBのロックと同時実行制御というタイトルの記事を作成し、つい最近公開しました。

読んで字のごとく、(数年前の僕も含め)あまりにみんなのファイルロックの使い方が下手だ、ということで、決定版の解説を作ろう、と思ったものです。

http://jn.swee.to/cano/lock/index.shtml

懐かしい...とまず思ってしまう.
昔CGIを書き始めた頃は,flockではうまくロックできない,と書かれたWebサイトをよくみかけたものです.

単に使い方が間違ってるだけだと思いますが,当時は人気サイトにそう書かれていたので信じている人が多かったように思います.


----

で,文章を読んだところいくつか微妙な記述があったので指摘してみる.

文章の中で,ロック・排他制御と,トランザクションを似たようなものとして扱ってますが,異なるものです.

トランザクションに関しては
http://www.microsoft.com/japan/msdn/library/default.asp?url=/japan/msdn/library/ja/cpguide/html/cpconacidproperties.asp
などが参考になると思いますが,ロックより抽象的な概念です.
また,ロックで実現できるのは,トランザクションの独立性だけです.

ロックにも複数あり,flockやMySQL(MyISAM)のテーブルロックのようなRead/Writeロックもあれば,MySQL(InnoDB),Oracle,Postgresであるようなもっと複雑なロックもあります.
mutex,クリティカルセクション,Javaのsyncronizedなどはもっと単純で,単に排他制御するだけです.

この辺は混ぜずに話した方が,わかりやすいかと思います.
(特に後々DBを扱う場合に,flockのつもりだと困ることも多いかと...)


プログラム2の例

if (-e "lockfile.lock") {sleep(1);} #ロックファイルが存在する間、1秒ずつ待つ

ロックファイルが存在したら1秒待つ処理と直すか,コードが間違ってますね.

while (-e "lockfile.lock") {sleep(1);}

でも,こんなコードにするとプロバイダから怒られるのでまずそうですね.
実装例として,上限回数5回くらいでリトライするものを見たことがあるので,例にはそういうものが良さそうです.


プログラム3

print OUT $count;
close OUT; # ロックは自動的に解除される

丁寧に自分でロックを解除しない方がよいことも触れた方がいいかもしれません.

print OUT $count;
flock OUT, 8;
close OUT; # ロックは自動的に解除される

と書くと,Perlのバージョンによってはうまく動きません.
printのバッファがフラッシュされる前にロックが解除されてしまうからです.

もうだいぶ前に対処されて,今はflock解除時にバッファが自動的にフラッシュされるようになっているはずですが,すごく古いPerlを未だ使っているかもしれないので,補足として.


flockの動作について.

flockはアドバイザリロックで強制ロックでは無いことを説明に加えた方がよさそうです.
flockはあくまで「宣言」であって,ファイルを操作するすべての場所でロックを使わないと意味がないことを理解していないと,書き込み処理の時は読み込み→書き込みの一連の処理を正しくロックして行うものの,読み込み処理のみの時はロック一切なしで処理してしまう,ということをやりかねません.

説明の文章の中では,ロックを取得することと,ファイルに読み書きできることが同列で扱われているので,その点誤解される可能性がありそうです.


負荷ツールに関して.

http://www.apache.org/

Apache付属のabというコマンドラインのツールや,Jakarta Projectの中にあるJMeterなんかを紹介すると良いかもしれません.


DBの話.つっこみ箇所が多いかも.(^^;

どこまで安全に、広い範囲をロックするか、を、リレーショナルデータベース(以下単にデータベース)の用語では「隔離レベル」と言います。

http://www.techscore.com/tech/sql/11_03.html

あたりを読んでみてください.
ちょっと正しい説明からかなりはずれてしまっていると思います.

でも、既に大抵の問題は説明しました。ここまでのCGI編で述べた「原則」を理解していれば、DBにおける排他処理を理解したも同然です。

MySQL(MyISAM)に関してはほぼ同じですが,MySQL(InnoDB),Postgres,Oracleなど,マルチバージョニングを採用しているシステムでは,その機構について理解が必要です.
正しく理解できていないと,デッドロックなどが発生して悩むことになると思います.

また,MySQLは,デフォルトエンジンはMyISAMですが,他にもInnoDB,BDB,NDB,MEMORYなどのデータベースエンジンがあって,それぞれで排他制御周りの実装まで異なっています
MyISAMでしか通用しない話は,そのように書いた方が誤解が少ないです.

InnoDBで TABLE LOCK しても思ったようには動作しない可能性があります.

7.5.15. InnoDB テーブルの制限事項

MySQL の LOCK TABLES 操作では、すでに完了した SQL ステートメントで設定された InnoDB の行レベルロックが考慮されない。つまり、あるテーブルで他のユーザのトランザクションが行レベルロックを設定していても、そのテーブルでテーブルロックを取得できる。したがって、テーブルで実行した操作が他のユーザのロックと衝突した場合は、操作が待機状態になる可能性がある。また、デッドロックが発生する可能性もある。ただし、InnoDB によって設定される行レベルロックでは常に完全性が配慮されているため、デッドロックによってトランザクションの完全性が損なわれることはない。 また、テーブルロックのために、他のトランザクションはテーブル上で(矛盾するロックモードで)行レベルロックを追加で取得できなくなる。

次の説明も条件によって間違いです.

これらの明示的なロックを行わない場合、MySQLはレコードを書き込んだり変更したり削除する際にその瞬間だけテーブル全体を自動的にライトロックし、

場合によっては,読み取りとINSERTが同時に行えます.
したがって,ライトロックが自動的に行われるわけではありません.

7.1. MyISAM テーブル

データファイル内に空きブロックがないテーブルに対し、他のスレッドがそのテーブルから読み取りを行うのと同時に新しいレコードを INSERT できる(同時挿入)。空きブロックは、大量のデータを含んだ可変長レコードに含まれるデータの長さが短くなるような更新をした場合、またはレコードを削除した場合に発生する。すべての空きブロックを使い切ると、それ以後の挿入は再び同時挿入になる。

※まあ,データが壊れることはないですが...

さて、MySQLはテーブル単位でしかロックできないので、300MBのテーブル全体を走査して検索するような事態が発生すると、数秒間~数十秒間、他の接続がそのテーブルの排他ロックを得られなくなって立ち往生してしまう、という問題が起きます。MySQLのデザイン上の制約なので、そういう検索が起こらないよう、全てが一瞬で終わるように工夫しましょう(方法はたくさんあるのでマニュアル参照)。どうしてもこれが避けられない場合にはPostgreSQLを検討します。

InnoDBを使いましょう.
MySQLと同じSQL文法ですし,MyISAMと混在させて使うことも出来るので,テーブル毎の用途に合わせてエンジンを変えることもできます.
(異なるエンジンのテーブルを一つのトランザクション内で使用するのはお勧めできませんけど)

明示的にユーザが何かをロックする、ということはありません。PostgreSQLは、中で複雑なことをして、自動的に必要なものをロックしてくれているみたいです。

明示的にロックしないとデッドロックすることが多くなります.
まあ,コミットで失敗したらやり直せば...というのも確かですが,最初から失敗しないようなSQLを書く方がよいですね.

また,トランザクションの中でも,同じSELECT文を2回実行したのに,結果が異なることがあったりします.
この辺をどこまで厳密に他のトランザクションの影響から隔離するか,というのがトランザクション隔離レベルです.
一番高い Serializable にすれば安全ですが,パフォーマンスは落ちるので,デフォルトは Serializable より低い隔離レベルの事が多いです.

Postgresに関してはここに説明があります.
MySQL(InnoDB)に関してはここに説明があります.


というわけで指摘はここまで.

DBに関しては仕事でやっている人でもちゃんと理解している人は少なかったりします.
デッドロックがまれに発生していて,しかもデッドロック時にトランザクションの再実行もしてない...なんてのは実際に経験したことがあります(^^;

この辺はDBのコンソールを2つ開いて,2つのトランザクションをいろいろな形で同時実行させてみると理解が深まるかと思います.

|

« 蒼星配列を取り込んだACT配列での句読点 | Main | 理想のマウス? »

Comments

Post a comment



(Not displayed with comment.)


Comments are moderated, and will not appear on this weblog until the author has approved them.



TrackBack


Listed below are links to weblogs that reference CGIのファイルロック問題:

« 蒼星配列を取り込んだACT配列での句読点 | Main | 理想のマウス? »