戻る

2.ファイルへの同時書き込みの防止
掲示板やチャット、カウンタなど、データ保存をするタイプのCGIでは、ごくまれに、アクセスが集中した場合にデータ破損が生じる場合があります。

ほとんどの場合、これらは同じデータファイルに同時に起動(アクセス)されたCGIから、同じタイミングで書き込みが行われる事から生じます。

そこで、これを防ぐような方法を考えていきたいと思います。

(1) flockを利用して防止
UNIX系のサーバーのPerlでは、flock関数を使ってデータの読み書きの際にファイルロックを行う方法が簡単です。flockにはいくつかの動作モードがあります。ここでは、誰かがファイルにアクセスをしている際は、そのアクセスが終了するまでプログラム内部で待機するようなモードを利用します。

読み込み部は次のようになります。    if (!open(DB,"$log_file")) {     &error("ログファイルが読み出しオープンできません。");    }    flock( DB, 1 ); # ファイルを読み出しロック    @lines = <DB>;    flock( DB, 8 ); # ロック解除    close(DB); 書き込み部は次のようになります。    if (!open(DB,">$log_file")) {     &error("ログファイルが書き出しオープンできません。");    }    flock( DB, 2 ); # ファイルを書き込みロック    print DB @new;    flock( DB, 8 ); # ロック解除    close(DB); このように、比較的簡単にファイルのロックができるflockですが、残念なことにすべてのサーバーで利用できるとは限りません(特にWindowsNTベースのサーバーがそうです)。 また、flockがサポートされていない場合、致命的エラーを出してCGIスクリプトの実行が止まってしまいますので、事前に十分に調査をした上で利用するか、evalを使ってエラー対策をしておく方がよいでしょう。

   例:eval {flock( DB, 1 );}; # ファイルを読み出しロック また、通常のPerl環境では、ファイルの書き込みの際にバッファリング処理が行われていますので、スクリプトではファイルに書き込んでいるつもりでも、実際には書き込みが行われていない場合があり、タイミングによってはロック解除の方が実際の書き込みより早くなってしまう場合があります。

これを防ぐために、プログラムの先頭付近(サブルーチンinit内など)で、Perlの予約変数 $| を使ってバッファリングを行わない(出力の際にフラッシュを行う)モードに設定しておく方がよいでしょう。

   $| = 1; # ファイルバッファリンクを行わないモードの設定 上記の処理を、サブルーチンread_dataとwrite_dataに追加します。
   変更後のサブルーチン

(2) symlinkを利用して防止
つづいて、symlinkを使ってファイルロックをする方法を紹介します、この方法は、現在Web上で公開されているPerlスクリプトで、もっとも数多く利用されている方法ではないでしょうか。

まず、symlinkについてですが、この関数はあるファイルに対して別な名前で関連つける(シンボリックリンク)機能をもっています。そこで、あるファイルに対してシンボリックリンクが成功するかしないかを利用して、リンクに失敗したときはデータファイルが使用中と判定するようにします。

実際には、symlinkが成功するまでwhileでループさせますが、1ループごとに待ち時間をおいて、一定回数のループの後はエラー終了するようにするのがよいでしょう。また、リンクのために、あらかじめ lockdir というディレクトリを作成し、パーミッションを666に設定しておく必要があります
   $lockfile = "lockdir/lock";    $count = 0;    while ( !symlink("$$","$lockfile") ) {     if ( $count == 5 ) {     &error("ただ今アクセスが集中しています。");     }     $count++;     sleep(2);    } ロック(リンク)の解除はunlinkを使います。    unlink($lockfile); このsymlinkも、サーバーによって使用できない場合があり、Perlが致命的エラーを起こして異常終了するので注意が必要です。

また、何らかの原因でsymlinkでリンクした状態のままCGIの実行が終了した場合、ロックがかかったままの状態になります。そのような場合、ロックルーチンで作成したファイルをFTPで削除する必要があります。

    ****

上記の処理を、サブルーチンread_dataとwrite_dataに追加します。
   変更後のサブルーチン

(3) その他の方法
flocksimlinkを使う方法は、簡単、あるいは確実な方法なのですが、残念なことにこれらの関数はすべてのサーバーで利用できるものではありません。もし、これらの関数が利用できないサーバーの場合、ファイルロックをあきらめるか、これらに変わる方法でロックするしかありません。

では、どのようにすればよいのか? いろいろ考えられますが、サブディレクトリのパーミッションを777に設定できる場合は、ファイルロックの状態を判定するためのダミーファイルを使う方法があります。つまり、書き込みにを行うにあたって、ある特定の名前のファイルが存在する・しないで、現在ロック状態を判定するわけです。

手順的には次のようになります。
   (1)lockというファイルが無いかチェック
   (2)lockがあればロック中の処理(待機あるいは終了)
   (3)lockが無ければ、lockを作成
   (4)データの書き込み
   (5)lockを削除

もっとも、この方法ではlockを作成するときに、もし偶然にもファイルの作成が同時に起こってしまったら、ファイルの衝突が起こる可能性があります。

****


ファイルの作成ができない場合などでは、ファイル名を変更する方法があるでしょう。
とにかく、何らかの方法でロックされている状態を知ることができればよいのですが、どの方法も決定的な方法では無いと思います。何か良い方法があれば紹介してほしいと思います。

以上の変更のうち、flock版をver0.92としておきます。
   TinyBoard v0.92

戻る
CGI工房