KITA Eng.

北海道でサーバー技術者として歩み出したひとが綴るblog。

Cronの自動処理を多重起動させないための方法を調べたら...。

photo by mag3737

 Cronで定期的に実行さている処理が、システム負荷だったり処理する量の変化によって、実行間隔よりも長くかかってしまうと、多重起動するという小事件が発生するのです。

 多重起動しても大して影響のないようなものならいいんですけ、多重起動によって余計にシステム負荷が増大したり、多重で実行されることでうまく動かないなんてこともあったりするわけで...。

 必要に迫られたので、その解決方法をいろいろと探ってみました。

ひとまずGoogle先生に問い合わせると...

 上のようなところが現れました。

 大雑把にまとめると、

  1. ロックファイルをつくる
  2. プロセスの存在を確認する

という手段のもよう。まぁ、そうなりますよね...。

どう実装するか(方針)

 ロックファイルをつくるのが良いのか、プロセスの存在を確認するのが良いのか…。

 異常終了でロックファイルがうまく解放されないとか削除されないという事件が起こると悲しいことになるということで、プロセスの存在確認の方向で進むことにしました。

 プロセスの存在確認の方法もいくらか考えようがあって、

  1. pidファイルを用意して、起動時にpidを書き込む
  2. 現在稼働中のプロセスから拾う

とかがよく出てくるやつのようですが、1.はロックファイル同様ファイルの書き込みが発生するので、ロックファイルを却下した後に選択するのは...。ということで、2.現在稼働中のプロセスから拾う作戦でいくことにしました。

現在稼働中のプロセスから拾う作戦の実装

 いくつか候補が上がったので、ひとまず列挙。(以下、対象となるCronで実行するスクリプト名を、"cron_job.sh"という名称ということにします。)

  1. ps -ef | grep -E "[c]ron_job.sh"
  2. pgrep -fo cron_job.sh
  3. pidof -x cron_job.sh

 プロセスがあるかどうか拾うといえばpsと思ってましたが、他にもpgrepとかpidofなんてコマンドもいらっしゃったのですね。

1. ps -ef | grep -E "[c]ron_job.sh"

 プロセスの存在確認でよく見る気がする書き方ですが、おまじない的にgrep正規表現なんぞ使っていてうっとおしいので、却下とします。いや、別に正規表現が嫌いとかじゃないんだよ、謎の[ ]の意味を思い出すのがめんどくさいんだよ...。

2. pgrep -fo cron_job.sh

 Man page of PGREP

 -fオプションを使うことで、プロセス名だけではなくて、コマンドライン全体をマッチ対象にできる。これをつけるかつけないかがちょっとした分かれ目。

 cronで実行するスクリプトに実行権限をつけて実行させている場合は、-fなしでよさそうだけど、phpスクリプトとかで、/usr/bin/php /path/to/script/cron_job.phpみたいにcronで呼んでいると、cron_job.phpの部分は-fなしだとマッチしてくれないもよう(/usr/bin/phpの引数として与えられているだけの値という扱いだからだろう)。

 とりあえず-fつけときゃいいんじゃねと思いつつ、ちょっとした落とし穴もあって、-fをつけるとコマンドライン全体がマッチ対象になって、部分一致でも引っかかる。あまりないかもだけど、cron_job.shでマッチさせようとしたのに、cron_job.sh.v2とか謎のやつも動かしてたりすると...。多くの場合はほとんど気にならないパターンでしょうか。

 プロセスの抜出はこいつでうまくいきそうなので、実際に多重起動を防ぐ形でのcronへの登録は、

10 * * * * user if [ "$$" = "`pgrep -fo cron_job.sh`" ]; then /path/to/script/cron_job.sh; fi

こんな形としました。最初はは、testの部分を"" = "pgrep -fo cron_job.sh"としていたのですが、うまく動かず。cronで呼ばれた時点でpgrepには自分がマッチする状況になっているので、空にはならないということなんでしょう。一番古いpidと自分のpidが一致すれば自分しか起動していないという、判断をするという形にしました。

 ということで、今回は一応これで解決としました。

3. pidof -x cron_job.sh

 Man page of PIDOF

 2.で解決したので、こっちの検証はいらなかった気もしないでもないですが...。せっかくなので勉強がてらこっちでの方法も検討しました。

 こちらは、「名前でプロセスを見つけ、それらの PID を一覧表示する」というコマンドということで、2.の検証でちょっと問題に上った、

 cronで実行するスクリプトに実行権限をつけて実行させている場合は、-fなしでよさそうだけど、phpスクリプトとかで、/usr/bin/php /path/to/script/cron_job.phpみたいにcronで呼んでいると、cron_job.phpの部分は-fなしだとマッチしてくれないもよう(/usr/bin/phpの引数として与えられているだけの値という扱いだからだろう)。

が影響します。こいつで実装する場合は、スクリプトに実行権限を与えておいてあげて、直接実行してあげる形にする必要があるようです。

 で、このパターンで実際に多重起動を防ぐ形でのcronへの登録は、

10 * * * * user pidof -x cron_job.sh >/dev/null || /path/to/script/cron_job.sh

 こっちはすっきりしました。コマンドライン全体がマッチ条件にならないこと、指定したコマンドのプロセスがひとつも見つからなければ、0以外のリターンコードを返すを利用すると、ifを使わずに||を使ってあげれば、すっきり書けるもようです。

 cronで実行するスクリプト自体に実行権限がついているなら、こっちのほうが良いかもしれません。

雑感

 今回は、ファイルを読み書きせずに、既存のスクリプトには手を加えずに、なんか追加でインストールしたりせずにという条件の中での実装となったので、こんな感じです。  Google先生に尋ねているときにちらほら見かけたのは、あんまり間隔の短いスパンで動かすならデーモン化...みたいなこともあったりしたので、そういう方向もあるんでしょうねぇ。  よく使うコマンドをひたすらパイプで組み合わせていく作戦もいいんですけど、結構、要件にマッチしたコマンドって埋もれているもんだなぁと思ったのでありました。