プロが教えるわが家の防犯対策術!

perlで書いたCGIの処理時間が非常に長いのです。
処理は数百名に対してメールを配信するもので、テキストで書かれた数百のメールアドレス配列に対してループ文で一人ずつメールを配信しています。これが約4分くらいかかります。
質問は2つあって、
(1)まずこのメール配信の処理を早くする方法はないでしょうか?
 配信アドレスは自動的に追加されたり削除されたりするので固定のML
 を使う事ができませんので、個別にメールを送る方法を取っていま  す。
(2)メール配信をバックグランドで実行させて、メインのプログラムは
 復帰させる、という方法が出来るでしょうか?
 今は、メインCGIが画面の表示やメールの配信などほとんどを実行し
 ています。
 このような場合、メール配信中はそのメインCGIが実行権を握った
 状態になる為、他のユーザはこのメインCGIを実行出来ないと思って
 いるのですが、間違った認識でしょうか?
 もしそうだとすると、早くメインCGIの実行権を他のユーザに明け渡 さなければならないと思うので、メール配信を別CGIで実行させたほ うが良いのかな?と思った次第です。

 よろしくご教示下さい。

A 回答 (3件)

がると申します。


1番については「処理次第」としか言いようがないのですが。#1さんの補足質問のコードを見てる限りですと「普通はこんなもん」だと思うです。
ここから速度アップを考えると、smtp関連で結構突っ込んだコードになってくるので、「業務的に必須」とか「やる気が両手からあふれかえっている」とかでなければ、このままでよいように思うです。
ちなみに、$mailfile を読み込む際の
flock(OUT,8);
は不要です。
# flockの解除は「しない」癖をつけたほうが安全です。

んで。
私は「まとめて送信」には比較的懐疑的です。
一つには、ユーザ的に「メールヘッダのToが自分のアドレスである」ほうが好感度が高いのが一つ。
もう一つが、まとめて送るときはSMTP的に「RCPT TO」とうコマンドを列挙するのですが、これには数量限界がありまして。RFC的にもさほど多くない上に(ちょっと記憶に浅いのですが、たしか100~200程度)、実装上ではさらに低い上限(たしか10~20くらい)もありまして。そのあたりから考えるに、まとめての送信はちょっと微妙です。

2番についてですが。
forkなどで別プロセスをたたき起こすことによって「バックグラウンドでの実行」は可能です。ただ、厳密にはforkで作った子プロセスには2パターンの挙動がありまして。さらにHTTPdの挙動も微妙で。
まずforkの子プロセス周りですが。「子プロセスが実行中に親プロセスが終了。子プロセス君、君はどうする?」と質問すると、「んじゃぁ親プロセスのもう一つ上(大抵はプロセスID 0のプロセス)と親子関係結びなおしてそのまま実行」という独立心旺盛な場合と「親が死んだんだもの、僕も一緒に~」という情愛豊かに終了してしまう場合とがあります。
次にHTTPdですが「親プロセスしか監視しない」タイプと「そこから派生した全てのプロセスをチェック」するタイプがやはり存在します。
ですので、可能か不可能かは「環境次第」になってしまいます。
環境に依存しないためには、
・CGIで「メール送信依頼」をどこか(ファイルまたはDB)に設定する
・cronで定期的に送信依頼をチェック、依頼があれば遂行する
というほうがより安全です。

ちなみに「メール配信中はそのメインCGIが実行権を握った状態になる為、他のユーザはこのメインCGIを実行出来ない」ってのは明確に誤りです。
細かく書くと長くなるので省略しますが、基本的にプログラムは「あちこちから呼ばれてパラレルに動くことが可能」です。そのあたりはプロセスとかその辺を勉強すると色々わかると思います。

以上、なにか参考にでもなれば幸いです。

この回答への補足

がるさんありがとうございます。
>私は「まとめて送信」には比較的懐疑的です。
>一つには、ユーザ的に「メールヘッダのToが自分のアドレスである」
>ほうが好感度が高いのが一つ。

↑の件については、まさにその通りで、だからこそ、1通ずつメールを配信する事で、ユーザには個別にメールが配信さるようにしています。
でも時間が掛かってしまっているのが現状の悩み。(T_T)
そこで、Aruku-20030515さんの言われている1回で送信できて、送信先に他のメールアドレスが載らない(個別送信)方法を是非アドバイス頂きたいと思った次第です。
flockの件、アドバイス頂きましてありがとうございます。
(2)については難しい(私にはまだ)内容ですね。勉強させてもらいます。
(1)の1回で送信で実現できないかに望みをかけたいのですが。。。

補足日時:2005/11/30 15:27
    • good
    • 0

(1)


送信するメールの内容が同一であれば、宛先が可変であっても、1回の送信で全員に送信できます。

ループ処理で1通づつ個別に送信するのは、サーバーに負荷を与え、場合によってはサーバー管理者から該当CGIの使用を禁止される場合があるので、改善必須だと思われます。

(2)
WEBサーバーから起動されるCGIは、1セッションにつき1つ起動されます。つまり、2つのクライアントがほぼ同時にセッションを開始すると、CGIは個別に2つ動きます。

言い換えれば「どんなに長い処理をしようが、他のユーザーが待たされる事は無く、待たされるのはCGIを呼び出したユーザーのみ」です。

つまり、実行権の明渡しは考えなくて良いのです。実行権の明渡しなどのマルチタスク処理はOSが勝手に行います。と言うか、実行権を1つのタスクで占有する方が遥かに難しいです。

ここで重要になるのが「CGIがアクセスする資源がセッションごとにユニークではない場合」の処理です。例えば、メールの通し番号を記憶しているファイルなどがそれに該当します。

以下のような処理が2つ同時に動いたらどうなるか考えてみて下さい。
1.連番ファイルから番号(例えば10)を取り出す。
2.番号に1を加えて11にする。
3.連番ファイルに更新した番号11を書き出す。
4.メールに「11」との連番を付けて送信

2人のユーザーが個別に処理を呼び出すなら問題は起きないですが、殆ど同時に呼び出すと以下のように変な事になります。
1-A.連番ファイルから番号(例えば10)を取り出す。
2-A.番号に1を加えて11にする。
1-B.連番ファイルから番号(例えば10)を取り出す。
3-A.連番ファイルに更新した番号11を書き出す。
2-B.番号に1を加えて11にする。
4-A.メールに「11」との連番を付けて送信。
3-B.連番ファイルに更新した番号11を書き出す。
4-B.メールに「11」との連番を付けて送信。

このように、同じ番号のメールが2つ送信されてしまいます。この例は単純な連番処理ですが、配信先アドレスリストにも同様な事が言えます。

Aさんがアドレス削除中に、Bさんがアドレスを追加すると、Aさんのアドレスが削除されずに残ったり、Bさんのアドレスが追加されずに消えたりします。

これを防ぐには、該当ファイルに対して排他処理をしなければなりませんが、ここが非常に難しいのです。排他制御でロックを掛けたままCGIがエラーで終ると、ロック解除する人が居なくなり、永久にロックされる状態になります。また、複数のセッションがお互いをロックし合い、どちらも先に進む事ができずにデットロック状態に陥るのを防ぐ必要も出て来ます。

その為、このような排他制御が必要な共有データを処理する場合、そのような面倒な部分を全部自動的にやってくれる「データベースエンジン」を使うのです。

多数のユーザーが頻繁にアクセスし、しかも、同時にアクセスされる可能性のあるCGIを安定的に動作させるのは、かなり難しいです。頑張って下さい。

この回答への補足

はい、(1)については送信する内容は全て同じです。
是非、1回で送信できる方法をもう少し詳しくアドバイス頂ければ大変助かります。
(2)の例については分かりやすく理解できました。が、排他処理についてはよく考えないと難しそうですね。^_^;
こちらはもっと勉強してみます。
何卒(1)の実現案をアドバイスして下さい。m(__)m
よろしくお願いします。

補足日時:2005/11/30 15:31
    • good
    • 0

1については


適切に処理させれば「一度の送信」で処理できますよ。
(当然、あて先に他人のメイルアドレスが載る事はないです)

2.シェルを適切に処理させていれば当然
その他の処理は動作可能ですよ
どういうコードを書いているのか不明なので
これ以上は分かりません

この回答への補足

さっそくの回答ありがとうございます。
(1)に関して、是非アドバイス頂きたいと思います。
メール送信のプログラムは以下のように書いています。
注)$mailfile:メールアドレスが書かれたテキストファイル
$Title:メールのサブジェクト
$message:メール本文

if(!open(OUT,"$mailfile")){&error(err_file);}
flock(OUT,2);
@MAILDATA = <OUT>;
flock(OUT,8);
close(OUT);

$count_mail = @MAILDATA;
if($count_mail > 0){
$i = 0;
foreach $line(@MAILDATA){
($mailaddress,$mail_name) = split(/\,/,$line);
open(MAIL, "|$send -t -f '$myaddress'");
print(MAIL "From: AAA <$myaddress>\n");
print(MAIL "To: <$mailaddress>\n");
print MAIL "Subject: $Title\n\n";
print MAIL $message;
close(MAIL);
$i ++;
}
}
上記のプログラムだと、$mailfileを1行ずつ読み込んで、一人ずつメールを配信する事になります。従ってメールの配信リスト分のループが発生して時間がかかっています。
これを、一度の送信で処理できる方法をアドバイス頂けますでしょうか?
よろしくお願い致します。

補足日時:2005/11/30 14:15
    • good
    • 0

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