サーバが重かった原因は別だったんだけど、某サービスでメールを受信してプログラムを起動する際のコストが大きいんじゃないかという話をしたので、以下のようなことを考えてみた。
qmailを使っている場合、届いたメールをプログラムで処理したい場合は .qmail に
| /path/to/program.pl
と書きます。標準入力にメールの内容が渡され、環境変数等を通して表書き発送者アドレス等が取得できます。postfix でも master.cf にtransportを追加し、mapファイルを変更すると大体同じことができます。
参考: RailsとPostfixで受信メールを処理する方法
ただし、この方法だとメールの受信の度にプログラムを起動(exec)するコストが高くなりがちです。特にデータベースに接続したり、アプリケーションのライブラリを読み込んで行くと起動の負荷が上がって行ってしまいます。そこでメールから起動するコマンドをアプリケーションから切り離して軽量に実装し、常駐デーモン(アプリケーションサーバ)にHTTPやキューを通して渡すということを行うと思います。mod_perlに渡すとか昔つくりました。
ただ、この軽量のプログラムもまだ重いし、エラー処理が面倒だなぁと思っていたので、Maildir をキューとして使って、直接jobqueueで処理してしまう方法を考えたみた。
この方法では、まず任意のディレクトリにメールを配送します。.qmail でも
/path/to/dir/maildir/
とディレクトリ指定できます。最後のスラッシュがないとmailbox形式になってしまうので注意。postfixでもvirutalhost機能を利用すれば同じ設定できると思われます。メール はMaildir 内の new サブディレクトリに保存されます。
受信したメールを処理するjobqueue workerにはこのnewディレクトリを監視し、新着のメールがあれば都度処理を行うループを実装します。これでメール受信の度のforkの必要がなくなるので負荷の削減にも繋がりそうです。またメールの処理中にエラーが起きた時はファイルをそのままにしておくだけであとで再実行できるのが楽です
今回作ったサンプルには、前々回のエントリーで書いていたように、workerは複数プロセス起動し、MaxRequestPerChild機構もいれてみました。実装はgistにて
複数のプロセスで1つのディレクトリを読むので、flockして競合をさけています。またLinuxだとInotify2を使ってディレクトリを監視するので、新着のメールがあった場合瞬時で処理を開始することができます。上の例では何も処理はしていませんが、実際使うなら67行目のtry-catch内にアプリケーションロジックを書くことになると思います。
Maildirでは通常メールを処理するとき、newディレクトリからcurディレクトリにrenameして処理を行うことになってますが、今回は目的が1つなので無視しますた^^;
twitterに思いつきを書いていたところ
@kazeburo qpsmtpdで受けてjob queueに突っ込むとかやってます
というご意見を頂きました。qpsmtpdは以前から気になってるけどMTAを入れ替える踏ん切りが付かないところ。実際の運用例があれば参考になるなぁー♡
@satoh_fumiyasu さん参考となる情報ありがとうございました