綾小路龍之介の素人思考

cronのジョブをランダムに走らせる

一時間に1回コマンドやシェルスクリプトを走らせたいけれど、走るタイミングはランダムであってもらいたい。呼び出しのタイミングが一定であればcronだけでOKだが、ランダムなタイミングだとそういうわけには行かない。そんな場合にどうするのか。1時間に1回という点はcronで、ランダムなタイミングはsleepで解決してみる。呼び出されるコマンドやシェルスクリプトの一般性をなくさないために呼び出すタイミングをランダムにするためのシェルスクリプトを介してコマンドやシェルスクリプトを呼び出す。

$ cat randomsleep.sh
#!/bin/bash
NUM=`expr $RANDOM % 3600`
echo $NUM
sleep $NUM
$ sh randomsleep.sh; wget http://www.google.com -O -

randomsheep.shで0-3599秒の適当な時間だけsleepする。其の後に適当なプログラムを呼び出す。このくらいならばシェルスクリプトにしなくても良いのかもしれない。

$ sleep `expr $RANDOM % 3600`; wget http://www.google.com -O -

ということで、これをcrontabに登録すれば「1時間に必ず1回だけ呼び出されるけれど、呼び出されるタイミングは呼び出されるたびに違う」という挙動を実現できる。登録のときは%をエスケープする(\%にする)こと。

$ crontab -e
0 * * * * sleep `expr $RANDOM \% 3600`; wget http://www.google.com -O -
  1. jijixi's diary - 書くネタが無い , wget で結果を標準出力に吐く
  2. シェルスクリプトでランダムな数字を得る方法 - World Wide Walker
  3. cron 登録ジョブ crontab のバックアップ
  4. crontabでは%をエスケープしなければならないなんて・・・ - はてな?のぐうたら玉子丼
  5. crontab - Wikipedia

この方法の問題はランダムな時間だけ、ジョブを呼び出したシェルとsleepが常駐してしまうことだ。これによってメモリが消費されてしまうので、ぎりぎりのメモリで頑張っているマシンでは辛い方法だ。例えば、psでジョブのプロセスIDをチェックして、topのバッチモード一回でメモリ使用量を確認すると下のようになる。

$ ps waxf
$ top -s -p 16929 -p 16932 -b -n1
top - 17:27:24 up 2 days, 20:51,  1 user,  load average: 0.29, 0.22, 0.19
Tasks:   2 total,   0 running,   2 sleeping,   0 stopped,   0 zombie
Cpu(s): 16.8%us, 22.2%sy,  0.1%ni, 50.6%id,  8.4%wa,  0.4%hi,  1.5%si,  0.0%st
Mem:    125988k total,   123352k used,     2636k free,      768k buffers
Swap:   369452k total,    57296k used,   312156k free,    38544k cached
  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
16929 hoge      18   0  4128 1420 1000 S  0.0  1.1   0:00.01 sh
16932 hoge      19   0  2744  468  408 S  0.0  0.4   0:00.00 sleep

topのVIRTで確認するとうちのコンピュータでは、ジョブが終了するまでの間、呼び出しシェルが4.1MBytes、sleepが2.7MBytesほど仮想メモリを占有してsleep状態になってしまう。1時間に一回という微妙な粒度でwgetを呼び出しているので、最長で1時間、最短で0秒間、平均で30分間、つまりこのジョブはメモリ消費の観点からすれば「6.8MBytesの仮想メモリを占有するプロセスが1日の内12時間走る」ことと同義である。これは無駄といえば無駄だな。

  1. @IT:httpd.confによるWebサーバの最適化(3/3)
  2. VIRT top - Google 検索
  3. topコマンドのバッチモード - カサヒラボ
  4. top バッチモード - Google 検索

さて、上のジョブを実現させたcronジョブにおける、一番の無駄な点はプロセスがメモリを占有した状態でsleep状態になってしまい、其の平均が30分間である点だ。crontabを自動的に書き換えるスクリプトを書いて、この無駄を排してみよう。

cronジョブは最短で1分の粒度で走らせることが出来る。そのため、1日1回当日24時間分のジョブをcrontabに追加出来れば、sleep状態になっている時間の平均を30秒に減らすことが出来、1日平均で24回*30秒=12分にsleep時間を減らせる。そこで、1日に1回0時0分にcrontabを書き換え、1時間に1回目的のジョブをこなすエントリーを追加し、過去に行ったエントリーを削除するシェルスクリプト(pushjob.sh)を書いた。

#!/bin/bash
COMMAND=$*
MIN=`date '+%M'`
HOUR=`date '+%M'`
DAY=`date '+%e'`
MONTH=`date '+%b'`
WEEK=`date '+%a'`
TMP_IN=`mktemp -q /tmp/$0.XXXXXX`
if [ $? -ne 0 ]; then
        echo "$0: Can't create temp file, exiting..."
        exit 1
fi
TMP_OUT=`mktemp -q /tmp/$0.XXXXXX`
if [ $? -ne 0 ]; then
        echo "$0: Can't create temp file, exiting..."
        exit 1
fi
CRON=''
#crontab -l
crontab -l > $TMP_IN
grep -v --regex "^[0-9]\{1,2\} [0-9]\{1,2\} [0-9]\{1,2\} [A-Z][a-z]\{2\} [A-Z][a-z]\{2\} sleep [0-9]\{1,2\}; $COMMAND;$" $TMP_IN>$TMP_OUT
for i in `seq 0 23`
do
        HOUR=$i
        MIN=`expr $RANDOM % 60`
        SEC=`expr $RANDOM % 60`
        CRON="$MIN $HOUR $DAY $MONTH $WEEK sleep $SEC; $COMMAND;"
        echo $CRON >> $TMP_OUT
done
crontab $TMP_OUT
rm $TMP_IN $TMP_OUT
$crontab -l
exit

実際にランダムに実行されるコマンドはpushjob.shの引数となるので今までのジョブでsleep `expr $RANDOM \% 3600`;としていた部分をsh pushjob.shに書き換え 、これを毎日0時0分に走るようにcrontabに追加

$ crontab -e
#0 * * * * sleep `expr $RANDOM \% 3600`; wget http://www.google.com -O -
0 0 * * * sh pushjob.sh wget http://www.google.com -O -

これで毎日0時0分にcrontabが下のような内容のジョブが追加され、過去に行ったジョブは消去される。単純にsleepを挟むよりも無駄なメモリ占有を抑えてジョブが走るはずだ。

9 0 13 Feb Fri sleep 40; wget http://www.google.com -O -
18 1 13 Feb Fri sleep 48; wget http://www.google.com -O -
27 2 13 Feb Fri sleep 57; wget http://www.google.com -O -
35 3 13 Feb Fri sleep 5; wget http://www.google.com -O -
44 4 13 Feb Fri sleep 14; wget http://www.google.com -O -
52 5 13 Feb Fri sleep 23; wget http://www.google.com -O -
1 6 13 Feb Fri sleep 31; wget http://www.google.com -O -
2 7 13 Feb Fri sleep 40; wget http://www.google.com -O -
10 8 13 Feb Fri sleep 49; wget http://www.google.com -O -
19 9 13 Feb Fri sleep 57; wget http://www.google.com -O -
27 10 13 Feb Fri sleep 6; wget http://www.google.com -O -
36 11 13 Feb Fri sleep 14; wget http://www.google.com -O -
23 12 13 Feb Fri sleep 53; wget http://www.google.com -O -
32 13 13 Feb Fri sleep 2; wget http://www.google.com -O -
40 14 13 Feb Fri sleep 11; wget http://www.google.com -O -
49 15 13 Feb Fri sleep 19; wget http://www.google.com -O -
57 16 13 Feb Fri sleep 28; wget http://www.google.com -O -
6 17 13 Feb Fri sleep 36; wget http://www.google.com -O -
15 18 13 Feb Fri sleep 45; wget http://www.google.com -O -
23 19 13 Feb Fri sleep 54; wget http://www.google.com -O -
32 20 13 Feb Fri sleep 2; wget http://www.google.com -O -
41 21 13 Feb Fri sleep 11; wget http://www.google.com -O -
49 22 13 Feb Fri sleep 19; wget http://www.google.com -O -
58 23 13 Feb Fri sleep 28; wget http://www.google.com -O -

前にも述べたようにpushjob.shの引数に与えたコマンドを追記するので、下のように全体を''で括ればランダムに行われるジョブの結果を捨てる様にcrontabに追加することもことも出来る。

$ crontab -e
0 0 * * * sh pushjob.sh 'wget http://www.google.com -O - 2>&1 > /dev/null'

これにより追加されるジョブは下のような感じ

9 0 13 Feb Fri sleep 40; wget http://www.google.com -O - 2>&1 > /dev/null;
18 1 13 Feb Fri sleep 48; wget http://www.google.com -O - 2>&1 > /dev/null;
27 2 13 Feb Fri sleep 57; wget http://www.google.com -O - 2>&1 > /dev/null;
35 3 13 Feb Fri sleep 5; wget http://www.google.com -O - 2>&1 > /dev/null;
44 4 13 Feb Fri sleep 14; wget http://www.google.com -O - 2>&1 > /dev/null;
52 5 13 Feb Fri sleep 23; wget http://www.google.com -O - 2>&1 > /dev/null;
1 6 13 Feb Fri sleep 31; wget http://www.google.com -O - 2>&1 > /dev/null;
2 7 13 Feb Fri sleep 40; wget http://www.google.com -O - 2>&1 > /dev/null;
10 8 13 Feb Fri sleep 49; wget http://www.google.com -O - 2>&1 > /dev/null;
19 9 13 Feb Fri sleep 57; wget http://www.google.com -O - 2>&1 > /dev/null;
27 10 13 Feb Fri sleep 6; wget http://www.google.com -O - 2>&1 > /dev/null;
36 11 13 Feb Fri sleep 14; wget http://www.google.com -O - 2>&1 > /dev/null;
23 12 13 Feb Fri sleep 53; wget http://www.google.com -O - 2>&1 > /dev/null;
32 13 13 Feb Fri sleep 2; wget http://www.google.com -O - 2>&1 > /dev/null;
40 14 13 Feb Fri sleep 11; wget http://www.google.com -O - 2>&1 > /dev/null;
49 15 13 Feb Fri sleep 19; wget http://www.google.com -O - 2>&1 > /dev/null;
57 16 13 Feb Fri sleep 28; wget http://www.google.com -O - 2>&1 > /dev/null;
6 17 13 Feb Fri sleep 36; wget http://www.google.com -O - 2>&1 > /dev/null;
15 18 13 Feb Fri sleep 45; wget http://www.google.com -O - 2>&1 > /dev/null;
23 19 13 Feb Fri sleep 54; wget http://www.google.com -O - 2>&1 > /dev/null;
32 20 13 Feb Fri sleep 2; wget http://www.google.com -O - 2>&1 > /dev/null;
41 21 13 Feb Fri sleep 11; wget http://www.google.com -O - 2>&1 > /dev/null;
49 22 13 Feb Fri sleep 19; wget http://www.google.com -O - 2>&1 > /dev/null;
58 23 13 Feb Fri sleep 28; wget http://www.google.com -O - 2>&1 > /dev/null;

この方法の問題点は、毎日0時0分crontabを書き換えるジョブがcronによって走らされてしまうため、このタイミングでcrontabを編集していると内容が変わってしまう可能性があることだ。

  1. シェルスクリプト 引数 - Google 検索
  2. bashで始めるシェルスクリプト基礎の基礎(1/2)
  3. 競合状態を避ける
  4. 【 mktemp 】 適当なファイル名の空ファイルを作成する:ITpro
  5. シェルスクリプト テンポラリ - Google 検索
  6. Manpage of CRONTAB
  7. crontab - Google 検索
  8. 【 crontab 】 プログラムを定期的に実行するcrondの設定ファイルを編集する:ITpro
  9. crontab 表示 - Google 検索
  10. 日付を取得する - UNIX & Linux コマンド・シェルスクリプト リファレンス
  11. linux date フォーマット - Google 検索
  12. Regular Expressions in grep
  13. grep regex - Google 検索
  14. シェルスクリプト入門 [制御構文]
  15. シェルスクリプト for 文 - Google 検索
  16. ユーザ crontab - Google 検

さて、同じ事を行うためにatは秒単位でもいけそうだという話を聞いて、crontabににatジョブを追加するような方向性も考えたのだが、余分にatdが走るし、atは5分毎にatrun(8)を呼び出すcrondという話もあるし、やめておいた。まぁ実際秒単位指定の出来るat入れるの面倒そうだったのでやめちゃっただけだけど。

  1. cron at 実装 5分毎 - Google 検索
  2. 【 at 】 指定時刻にジョブを実行する:ITpro
  3. at(1)
  4. at ジョブ 秒 - Google 検索
  5. at ジョブ 秒 linux - Google 検索
  6. cron at 秒単位 - Google 検索
  7. timespec - Google 検索

ソーシャルブックマーク

  1. はてなブックマーク
  2. Google Bookmarks
  3. del.icio.us

ChangeLog

  1. Posted: 2006-05-28T23:15:35+09:00
  2. Modified: 2006-05-28T08:28:36+09:00
  3. Generated: 2017-06-09T23:09:18+09:00