はじめに
担当サービスが育ってきて、自動化、非同期処理を作成する必要が高まりバッチ処理を書くことが増えてきた。
これまで3回バッチ処理を書いてきて、何回もハマってしまい、案件進行の遅延、不具合の発生など様々なトラブル引き起こしてしまった。
その反省と今後の対処、他の開発者には同じ過ちを犯さないでもらうためにPHPのバッチ処理作成のポイントをまとめることにした。
バッチ処理作成のハマりどころ
だいたい自分のように画面表示があるシステムの開発者が初めてバッチ処理を作成する時にハマるポイントは次の点だと思われる。
- バッチ処理特有のエラー
- サーバー間通信
- 失敗した時の原因探索
バッチ処理特有のエラー
PHPでバッチ処理を作っていざ実行してみると、途中で急に落ちてしまうことがある。
大抵のバッチ処理は、半日〜数日に渡る処理を実行するため、テストでは完全には同じ時間の実行をできないというのはありがちな話だ。例えば10,000件のレコードの加工・更新処理を行う時、テストでは1000件程度しか実行しないということが起こりうる。
しかしこれは結構危険で、10,000件のデータ更新と1,000件のデータ更新では全く意味合いが違う。
バッチ処理あるあるのメモリ管理問題だ。
PHPのデフォルトのメモリ割り当ては128MBとなっているため、これを超えると途端にPHPが落ちてしまう。(メモリリーク)
1,000件ではメモリリークが起きなくても、10,000件ではメモリリークが発生してしまう可能性がある。
対策
対策方法としては当然だが、メモリを食いまくる書き方をやめること。
具体的にはforeachなどでループ処理をする際に変数を解放しないと起こるので、使い終わった変数をunsetしてあげることが重要となる。
$records1 = get_records1();
$records2 = get_records2();
foreach($records1 as $record1) {
foreach($records2 as $record2) {
//なんか色々加工処理
}
}
メモリを大量に確保する可能性がある処理を書くときは、現在の使用メモリ量を出力するなどして、メモリ使用量が増大し続けていないかチェックするようにすること。
以下の関数でチェックできる。
echo memory_get_usage();
詳しくは以下参照。
http://kameryo.hatenablog.com/entry/2015/06/24/110559
上記の方法でもメモリが不足してしまう場合、以下のようにバッチ処理のはじめに記述しておけば、メモリの使用量を変更することができる。
ini_set("memory_limit", [適当なメモリ数]);
例えば自分は以下のように設定したりする。
ini_set("memory_limit", "512MB");
サーバー間通信
画面があるアプリケーション開発をしていると普段あまりサーバー間通信を意識することがない。
しかし、バッチ処理では複数のwebまたはDBサーバーへの通信を行う場合が頻繁にある。
接続するために書くことはIPやパスワードを間違えずに書けばいいだけなので、特段難しいわけではないが、
Apache等webサーバーの設定でハマったり、DBにユーザー作成を忘れていたり、地味に失敗することがある。
対策
ローカル環境やテスト環境でテストして上手くいったからって本番環境と差異が少しでもあるのなら、本番環境でも接続面で問題がないかテストをすること。
失敗した時の原因探索(ロギング)
多分もっとも大事なポイントかもしれないロギング。画面のあるアプリケーションでももちろん大事だが、それ以上に気をつけてログをどう吐くか設計したほうがいい。
少なくとも自分がした失敗の多くはロギングをちゃんとすればすぐに解決できるものが多く、今まで述べてきたハマりどころもここさえしっかり作っておけば、即座に解決できるはずだ。
対策
- 処理の各ポイントの完了部分でログを吐くこと(必要があればどのデータをどのように更新したのかログを吐く)
- エラー時にログを吐くこと
- 処理の開始、完了時にログを吐くこと
- 時間を必ず記録すること
- 当たり前だが改行すること
以上を守れば失敗した場合も調査が楽になるはず。
ちなみにログ出力は時間出力や改行を入れるようにロギング用のラッパー関数を作っておくと、捗るのでおすすめ。
例えば以下のようなstatic関数を作っておく。
/**
* ログ出力のラッパー関数 LOG_PATHには任意の出力先ファイルのパスが格納されている
* @param string $message
*/
public static function get_log($message) {
$log .= date( "Y年m月d日 H時i分s秒" )
$log .= " ";
$log .= $message;
$log .= "\n";
error_log($log, 3, LOG_PATH);
}
その他
普通はないと思うが、[curl URL]という叩き方でバッチを実行する場合、PHP実行時間が長いと途中で終了させる設定により、バッチ処理が終了させられてしまうことがある。
これはPHPのmax_execution設定がデフォルトで60秒になっていることが原因である。
対策
バッチ処理で初期設定として、
php_ini("max_execution",[適当な秒数]);
と書いておくとそのバッチ処理の実行時間を伸ばすことができる。
おわりに
ここまで書いてきた内容はバッチ処理を数回書いた程度の浅い経験に基づくものだが、おそらく同じような点で失敗する人が多いと思われる。
さらに大事な点やいいまとめ方がわかってきたら今一度バッチ処理についてまとめようと思う。
間違っている点やもっとここが大事だろっていう点があればコメントお願いします。