herokuは無料で使えて大変便利なサービスなんだけど、無料プランでは30分以上アクセスされないと自動でスリープするという制限がかかっている。
しかしherokuにはスケジューラーというcronみたいなサービスが別にあるので、こちらにcurl:webアプリのアドレス
のコマンドを10分に一回で登録してあげれば、10分に一回スケジューラーがアクセスしてくれるので自動スリープを防いでくれるというTipsがある。
検索をかければ山のようにそういう情報はでてくれるが、これは「24時間動かす」という前提になる。無料プランの場合、クレカを登録しておけば無料の場合でも31日24時間動いてても時間は余るようにはなってるが、スクリプトなどを回す場合そちらもカウントされ、時間を食い合ってしまう。ならば、「アクセスが大してない時間は自動スリープを意図して受け入れる」というのもありなんじゃないか?と思う。
で、具体的にそういう方法がどこにもかかれてないので、適当にメモ的にかいておく。
require 'time'
ENV['TZ'] = "Asia/Tokyo"
# 16時から翌7時まで、20分刻みの時間の配列を生成
timetable = []
hary = [*"00".."07",*"16".."23"]
mary = ["00","20","40"]
hary.size.times do |h|
mary.each do |e|
a = hary[h] + ":" + e
b = Time.parse(a)
timetable.push(b)
end
end
# 現在時間を600秒(10分)ごとに整形し、時間オブジェクトに変換
tm1 = Time.now.to_i / 600
now = Time.at(tm1 * 600)
# 現在時間が時間配列に一致したらアドレスを開く
if timetable.include?(now)
require 'timeout'
Timeout.timeout(10){
system("curl webアプリのアドレス")
}
end
herokuで実行するのでタイムゾーンはスクリプト内で指定してあげる必要があるので、ENV['TZ'] = "Asia/Tokyo"
は日本の時間帯前提で動かすのなら必須。
今回は午前7時から午後16時までは自動スリープを受け入れるので、それ以外の時間帯を["00:00","00:20","00:40","1:00","1:20",...]
といった配列に生成し、Time.parse()
で時間オブジェクトに変換する。
これだけだとherokuのタイムスケジュールと一瞬でしかマッチできないので、現在時間をUNIX時間に一旦変換し、10分ごとの判定になおすために600(秒)で割ってしまう。rubyの場合はインテグラのまま割り算をすると少数点以下は切り捨てなので、600(秒)をかけ直して時間オブジェクトに戻すと、現在時間は10分ごとの表記になる。
あとはさきほどつくった指定時間の配列に、今の10分ごとの時間が含まれているのかをチェックして、含まれていたらsystem("curl アドレス")
を実行しますよってやればOK。(Timeoutは保険で書いてるだけ)
あとはこのスクリプトをlib/script/などのフォルダにいれてherokuにpushして、herokuスケジューラーにrails runner lib/script/scheduler.rb
のコマンドで指定して、10分ごとの設定にしたらよい。
自分の思いつきでかいたのでもっとシンプルな方法があるのかもしれないけど、時間帯や日程の指定には結構簡単な書き方なので、よく使っている。
heroku外のcronでやりたい(PHP)
ただ、10分ごとにスクリプト回す性質上、当然これは頻繁にまわることになる。これ自体が無料時間を食ってしまうというのもあるし、なによりherokuの無料プランではwebアプリ以外を実行する枠は一つに制限されていて、他のスクリプトと頻繁に干渉してしまう。
なので外部のcronでもできるようにPHPなんかでも書いてみた。
<?php
$hour1 = range(0,7);
$hour2 = range(16,23);
$min = array("0","20","40",);
$hour = array_merge($hour1,$hour2);
$num = count($hour);
$add = 0;
$tt = array();
while ( $add < $num ){
foreach ($min as $val){
$a = $hour[$add] . ":" . $val;
array_push($tt,$a);
}
$add++;
}
$timetable = array();
foreach ($tt as $val){
$a = strtotime($val);
array_push($timetable,$a);
}
$now = time() / 600 ;
$now = ceil($now) * 600;
if (in_array($now,$timetable)){
$url = 'webアプリのアドレス';
$curl = curl_init($url);
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'GET'); // メソッド指定
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); // 証明書の検証を行わない
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); // レスポンスを文字列で受け取る
$response = curl_exec($curl);
curl_close($curl);
}
?>
やってることは全く同じなので特に説明の余地はない。PHPはぜんぜんわからないので適当だけど、今のところ動いてるから、まぁたぶんいいんだろう。たぶん。