アプリ版:「スタンプのみでお礼する」機能のリリースについて

Perlの変数に文字数制限(容量制限)はあるか

Perlで書いた自作の掲示板なのですが、ずっと普通に動いていたのですが
急にデータが欠けてしまいました。書き込みデータはテキスト形式で、
↓の様な形で保存しています。

<div>1つの書き込みの中身</div>\n
<div>1つの書き込みの中身</div>\n
<div>1つの書き込みの中身</div>\n
<div>1つの書き込みの中身</div>\n
<div>1つの書き込みの中身</div>\n

1つの書き込みは1行に収まっていて、書き込み時に \n を付けて保存して、
読み込み時は配列に読み込んで、べろっと出すだけの処理です。
掲示板書き込みなので unshift で上が新しい書き込みにしてあります。
通常のタグ禁止処理や、改行コード処理はしてあります。Perl5.6.1です。

数日前、容量が減っていることに気付き、調べてみると、

<div>1つの書き込みの中身</div>\n
<div>1つの書き込みの中身</div>\n
<div>1つの書き

のような形でデータが欠けていました。欠けていると言っても9割方消えていました。
残っていたのは新しい側の書き込みです。
いつ消えたのか、何をした時に消えたのかが不明のため、原因を探っている段階です。

もちろん、一番怪しいのはプログラムのミスなのですが、それも調べつつ、
ちょっと前から気になっていた点として、データ容量が1.5MBぐらいまで
ふくらんでいて重くなっていたんです。
data.dat のような1ファイルにテキストばかり1.5MB、そして内部の処理でも
普通にその容量を一つの変数に入れたりしています。
データが唐突にぶつっと切れていることと、容量が多くて気になっていたこと、
この辺りでちょっと怪しいのですが、変数の容量制限、ファイルの容量制限が
調べても出てきません。知っている方いましたら教えてください。

他にも、そういうバグの時こういうミスがあったよ、など、ありましたら
アドバイスをお願いします。
自分が作った物のデバッグで恐縮なのですが、よろしくお願いします。

A 回答 (8件)

変数なら、型により長さが規定されているでしょうね。


型で規定されている長さを超えるとダメでしょう。
Perl5.6.1 言語の仕様では?

この回答への補足

回答ありがとうございます。

型は……、ないです(よね?)。
その仕様がちょっと見つけられなくて。
今、教えてgoo内で「文字数の規定はありません」と答えている回答を
見つけました。メモリ関連は影響するようです。

補足日時:2010/06/29 11:48
    • good
    • 0

「ファイル容量の制限」は OS やら設定やらによるけど Perl は無関係.


「変数の容量制限」は, 「Perl言語」としては「int の大きさ」が影響したはず. あと, 当然だけど「1つのプロセスで使うことのできるメモリ量」にも依存する. だから, Perl処理系に (この辺の) バグがなければ 1.5 MB くらいでは死なない.
手元では Perl 5.10.1 だけど,
$a = join('', 'a' .. 'z') x 1000000;
くらいならまったく問題なく動きます.
ファイルの読み書きで競合しているって可能性はないか?

この回答への補足

回答ありがとうございます。

ファイルハンドラを見てみましたけど競合はしていませんでした。
ずっと問題なく動いていたんですよね。それでいきなり壊れたので
「ついに処理できる容量が限界を超えた」のかと思ったのですが。

「ファイル容量の制限」は違うんですね。
となると変数かメモリなのですが、変数の「intの大きさ」というのは
どの部分のことでしょうか? 
書き込みデータ処理には、変数や配列を使っていますが結構そのまま
ガンガン入れている状態です。

補足日時:2010/06/29 12:00
    • good
    • 0

ファイル読み書き時の排他制御が適切でなかった場合によく起こる症状に見えます。


flock()などでファイルロックをかけるのが一般的な排他制御ですが、書き込み時にopen(HANDLE, '>', ...)で新規書き込みオープンしてしまうのが最も典型的なミスです。
この場合、ロックがかかる直前に他のプロセスから読み込み処理が行われると空ファイルが読まれてしまうので、意図した動作にならずにファイル内容が失われる恐れがあります。
読み込み/書き込みのファイルオープン・クローズ処理に問題がないか確認されてはいかがでしょうか。
該当部分のスクリプトを公開して頂ければ、具体的なアドバイスが出来ると思います。

この回答への補足

回答ありがとうございます。
取り急ぎ書き込み部分を載せます。紙谷歌寿彦氏の本のやり方だったと思います。


$WriteComment
に今回の書き込みデータが入っている状態です。


# ファイルの追加書き込み処理
open( WRITE, "+< ../data/bbs.dat" );# 開く
flock( WRITE, 2 );# ロック
@OldBbsData = <WRITE>;
unshift( @OldBbsData, $WriteComment );
truncate( WRITE, 0 );# サイズを0に変更する(消す)
seek( WRITE, 0, 0 );# ファイルのカーソル位置の変更
print WRITE @OldBbsData;
close( WRITE );

補足日時:2010/06/29 13:56
    • good
    • 0

ファイルの排他制御に失敗した可能性が高いんじゃないかと思います。


2つのプロセスが同時に書き込んだりしたら、例えば、
あるプロセスが書き込んでいる途中のタイミングで、次のプロセスだデータ読み込みを実行
   →書き込み途中までのでデータしかしか読み込めない
ということになります。その後、最初のプロセス側では書き込み完了すれば、ファイルは正しい状態になりますが、

後のプロセス側で読み込んだデータは、その中途半端に読み込んだ壊れた状態になっているので、
後のプロセス側が書き込みを行えば、ファイルが壊れた状態に更新されることになります。

排他処理はファイルサイズに依存した問題ではありませんが、
ファイルサイズが大きくなると読み書きに時間がかかるようになるので、
タイミング的に問題が発生する確率は高くなります。

この回答への補足

回答ありがとうございます。

書き込み処理を行っている部分のソースを、回答番号:No.3 さんへの補足に載せました。
詳しく知識があるわけではありませんが、「flockでロックしているから大丈夫」、
という認識でした。解説本にもそうあったと思います。
それで、それがデータサイズが大きすぎて時間がかかった時とかのイレギュラー時の
処理なんですよね。。。

補足日時:2010/06/30 02:15
    • good
    • 0

現在の Perl の実装では, 文字列を「長さ+内容」という形で記憶します (C とは異なり「終端に特定の文字がある」という形ではない). この「長さ」を確か int で記憶しているはずなので, 「1つのスカラーに記憶できる文字列の長さ」は限界があります. 現状ではシステムによらず 32ビット符号付きで上限は 2GiB-1 だったかな? 配列やハッシュの要素数にも同じような制限があるはず.


ただ, #2 の最後にもちょろっと書いたけどそれとは無関係で #3 にいわれる排他制御の問題だと思いますよ. 「競合はしていませんでした」と書かれていますが, 「複数人が (ほぼ) 同時に掲示板に書き込む」状況を想定して調べましたか?
ああ, もう 1つ「遅延書き込み」という問題もあり得るか. 「同期書き込み不可能」とかいう腐った OS だと考えられなくもない.

この回答への補足

たびたびありがとうございます。

やはり聞く限り、2ギガビットとか全然行ってないですし、その辺の容量オーバーは
違う感じですね。排他制御ですが、本に書かれていたロックをかけるやり方をしていた
だけです。回答番号:No.3の補足にその部分を載せました。
同時書き込みや非常に重い時にも大丈夫かと言われると、flock に任せているだけで
少なくともテストはしていませんし、実行環境もそんな混み合ってはいませんでした。
そういう状況に耐えうる堅牢なロジックがあるなら念のため入れ替えておきたいところです。
でもその前に今回のデバッグですね・・・・・・。

補足日時:2010/06/30 02:22
    • good
    • 0

No.3で回答した者です。



追加書き込みのロジック自体は問題ないように見えます。
# 強いて指摘するなら、追記のみの場合はファイルサイズが小さくなることはありませんので、truncate()によるファイル全消去が不要なくらいでしょうか。
# seek(WRITE, 0, 0)だけで十分です。
# 余談ですが、ファイルサイズが減る可能性がある場合においても
# ファイルを全消去してから書き込むよりは、先頭へシーク+全データ書き込み後に
# truncate(WRITE, tell(WRITE));
# とした方が、ディスク処理のオーバーヘッドが少ないです。

で、書き込みが問題ないとなると、やはり読み込み処理に問題があるのではないでしょうか。
公開して頂いたサブルーチンを見る限り、おそらく「読み込み用」サブルーチンが別にあるのではないかと推測します。
# でないと、追記しない限り読めないことになりますから・・

Perlのflock()による排他処理は(OSにもよりますが)flock()を利用したファイルオープンに対してしか機能しません。従って、ファイル読み込み時にも
open(HANDLE, ...);
flock(HANDLE, 1); # リードオンリーモードでロック
といった処理が必要になります。
これを忘れた場合、質問者さんのような症状が発生する可能性があります。

この回答への補足

おっしゃる通り、全書き換えの時のロジックを、追記の時も使い回して、
まあ、余計な動きをしているけど動くからいいか、とそのままにしていました。
余計な部分は省くように参考にさせていただきます。


読み込み部分ですが、そのまま貼り付けます。
1ページに20件表示とかする用に行数($BbsCount)も取得しています。

# 書き込みデータの読み込み
open( BBS, "< ../data/bbs.dat" );
@BbsData = <BBS>;
$BbsCount = @BbsData;
close ( BBS );

flock してません・・・・・・。
つまり、読み込み時に競合があって書き込まれたため、開いたままのデータが
変なところで書き込み処理に割り込まれて、おかしくなったということでしょうか。
でしょうか、というか、その可能性があった、というところですかね?

補足日時:2010/06/30 02:31
    • good
    • 0

CGIを動かしているのがレンタルサーバ(などの第三者が設定した環境)であって、


壊れたファイルが0や8192バイトなどの切りの良い(?)サイズで切れているなら、
ファイル書き込み中にシグナルで殺されている可能性も考えられそうです。

時間のかかるCGIを制限しているレンタルサーバだと、
比較的短い時間でタイムアウトして、CGIにSIGTERMやSIGILLLが飛んできて、
プロセスが殺されることがあります。
ファイルサイズが増えれば、書き込みに要する時間も長くなるので。

もしそうだとすると、解決策は・・・
SIGTERMを無視してみても、最後にはSIGKILLが飛んでくると思うので、
処理を早く終わらせるしかなさそうです。

例えば、一定行数ごとにファイルをわけて、表示時はページ制御するとか。
表示&保存する件数を、全部はあきらめて最新何件などに減らすとか。

他には、limitでメモリ制限されてる、なんて可能性もありますが、
それだとファイル書き出しより前の時点で落ちるでしょうから、
今回の原因としては考えにくいですね。

この回答への補足

回答ありがとうございます。

サーバーはレンタルサーバーですが、壊れたファイルは 114,898バイトでした。
キリのいい数値ではなさそうです(たぶん)。
一応サーバーに、そういうことをすることもあるか聞いてみます。

動いていた時も書き込みに1秒ぐらいかかっていたので、これまでの累積を
いっぺんに開いている状態はやめて、もうちょっとすっきりさせたいと思います。

補足日時:2010/07/01 16:40
    • good
    • 0

No.3&6です。



> flock してません・・・・・・。
> つまり、読み込み時に競合があって書き込まれたため、開いたままのデータが
> 変なところで書き込み処理に割り込まれて、おかしくなったということでしょうか。
> でしょうか、というか、その可能性があった、というところですかね?

ちょっと説明が足りなかったので補足します。

読み込み時にflock()しなかった場合ですが、書き込みが純粋に追記だけの場合は(記事表示で途中で切れることはあるかもしれませんが)データファイルの破損までは至らないかなと思います。もちろん、読み込み内容が壊れるには違いないですので、読み込みルーチンにもflock()は追加すべきです。

データファイルが壊れるケースですが、既存記事の修正機能が実装されている場合などで、読み込みルーチンで取得した@BbsDataの値を使った書き込みルーチンが別にあった場合には、たとえその書き込みでファイルロックがされていたとしてもデータファイルが壊れることになります。

もっともその場合、データ変更に関する一連のロジック自体を見直す必要があるかもしれません。
既存記事の変更を行う場合には、「一度のファイルオープン+ファイルロックの間に、読み書きを全て済ませる」という処理に置き換えないと、ほぼ同時に複数の書き込みが発生した場合に一部のデータが失われるという別の不具合が発生する恐れがあります。ご参考まで。

この回答への補足

読み込み時、flock処理を追加してみます。

# 書き込みデータの読み込み
open( BBS, "< ../data/bbs.dat" );
flock( BBS, 1 );# ロック
@BbsData = <BBS>;
$BbsCount = @BbsData;
close ( BBS );


修正機能は付いていません。
でもそのうち付けようと思っていたので、付ける時にロック系、気を付けたいと思います。

とりあえず再開に向けて、処理するデータを小さくし、読み込み時に flock処理を追加してみます。
あと今回のことを一応、サーバー会社に聞いて返事を待つと。


それからもうちょっと頻繁にバックアップを取ります。。。
Googleにキャッシュが残っていたので少しサルベージしましたが、抜けが出てしまいました。
Google以外でそういうキャッシュが自動的に残っている可能性のあるサイトってありませんかね?

xxx.cgi に最初の20件、
xxx.cgi?page=1 だと21件~40件、
xxx.cgi?page=2 だと41件~60件、
xxx.cgi?page=3 だと61件~80件

という感じで表示させていたのですが、別ページ扱いなのでキャッシュが残ってたりするんですよね。

補足日時:2010/07/01 18:06
    • good
    • 0
この回答へのお礼

別の質問になってしまったので立て直します。
回答してくれた皆様、ありがとうございました。

お礼日時:2010/07/02 10:20

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!