heroku でアプリケーション作ってると、定期的にデータベースのメンテナンスをするなど、cron のような処理がしたくなってきます。ありがたいことに、Heroku Scheduler というアドオンを無料で使うことができます。
Heroku Schuduler では、10分毎、1時間毎、1日毎の間隔での処理を選択することができます。
ただし、気をつけないといけないのが、アドオンそのものは無料なのですが、アドオンにより処理を実行すると、dyno-hours としてカウントされます。月間750 dyno-hours までは無料で使えるのですが、それを超える場合は課金されてしまいます。まあ、月の日数が31日でも、744時間なので、処理に時間がかかるようなものを動かさなければ問題ないとは思います。
今回はデータベースメンテナンス用のタスクを実行したいと思います。具体的には不要レコードを定期的に削除するという処理です。
Play!Frameworkでスケジュールジョブの動かし方
結論からすると、Heroku へのデプロイ にあるHeroku 上でスケジューリングされたジョブを持つ Play 2 アプリケーション のとおりにやればOKです。
直接ソースを見たほうが早いのですが、備忘録代わりに書いておくと、ポイントは2つあります。
一つは、スケジュールジョブ用のクラス(リンク先のサンプルなら jobs/Tickjob.java )を作ってやって、ここに、static main メソッドを記述して実行したい処理を書いておきます。もし、実行したいジョブが複数あるなら、それぞれに対応したクラスを作ってやればOKです。
もう一つは、Procfileの定義で、通常のPlay!Frameworkのアプリケーションを動かすwebプロセスタイプに加えて、上記のスケジュールジョブ用のプロセスタイプを書いておくことです。リンク先のサンプルなら、下記のようになっています。
web: target/start -Dhttp.port=$PORT -DapplyEvolutions.default=true -Ddb.default.driver=org.postgresql.Driver -Ddb.default.url=$DATABASE_URL scheduledtick: java -Dconfig.file=conf/application.conf -DapplyEvolutions.default=true -Ddb.default.driver=org.postgresql.Driver -Ddb.default.url=$DATABASE_URL -cp "target/staged/*" jobs.TickJob .
追加したプロセスタイプ(scheduledtick)は、javaコマンドが定義したクラスを呼び出す形式になっており、クラスパスに、target/staged 配下を指定しています。また、呼び出し時の引数に .(ピリオド、カレントディレクトリですね)を指定しています。これは、TickJob.java内でアプリケーションのパスを取得するのに使われています。
target/stagedディレクトリについては、アプリケーションを本番モードで起動するおよび Heroku へのデプロイなどを参考にしてください。
(参考)
frameworkは異なりますが、やってることは基本的に同じだと思うので、参考になるかと思います。
Run non-web Java dynos on Heroku
ローカル環境でのテスト
ここまでできれば動作確認をします。
スケジュールジョブ用のクラスは、Play!Frameworkのアプリケーションから呼び出すことはできないので、上記のProcfileと同じ処理をする必要があります。
さて、どうしたものかと思うのですが、ちゃんと用意されています。Developing locally with Foremanにあるように、heroku toolbelt と一緒にインストールされている、foremanというコマンドを使えば解決です。
早速やってみます。まず、ローカル環境ですが、Play!Frameworkを本番用にコンパイルします。
play clean compile stage
問題なく処理が完了したら、
foreman start
とします。いろいろ画面に表示されるのですが残念ながらエラーが起きてます。Procfileを見ると、いくつか環境変数があるので、これが定義されていないのが原因のようです。
foreman(1) - manage Procfile-based applications
をみると、.envファイルを作成して、その中に環境変数を書いておけば反映してくれるようです。
なので、PORTとDATABASE_URLにローカル環境用の値を設定しておきます。
DATABASE_URL="jdbc:postgresql://localhost:5432/dbname" PORT=9000
これで、もう一度
foreman start
とするとスケジュール用ジョブを実行させることができます。
heroku上での確認
ローカルでの確認が終わったら、
git push heroku master
としてherokuにデプロイします。
Schedulerに登録する前に、作成したプロセスタイプを heroku上のone-off dyno で動作させてみます。
heroku run schduledtask
問題なく動作しているのであれば、Heroku Scheduler に登録します。
まずはアドオンを有効にします。
mor@T105-PandRDev:~/work/projects/pandr/vehicle$ heroku addons:add scheduler:standard Adding scheduler:standard on buslock... done, v20 (free) This add-on consumes dyno hours, which could impact your monthly bill. To learn more: http://devcenter.heroku.com/addons_with_dyno_hour_usage To manage scheduled jobs run: heroku addons:open scheduler Use `heroku addons:docs scheduler:standard` to view documentation. mor@T105-PandRDev:~/work/projects/pandr/vehicle$
あとは、ブラウザから、herokuのコンソールを開いて、
Add Job... を選択すると、コマンド名や実行間隔を入力する欄があるので、それらを指定して保存すれば完了です。
雑談
上記のやり方を見つけるまでにはいろいろとハマリました。
Schedulerアドオンがあるなら、じゃ、早速やってみようとして、ドキュメントを見てみると、Railsの場合は、rakeタスクというのを定義すればよいとあって、その他の場合はスクリプトなどを書けとあります。
いまから、rakeの書き方を調べるのも面倒なので、bashスクリプトでpsqlを処理すればいいかなと思って、heroku上にpsqlがあるかを調べると
mor@T105-PandRDev:~/work/projects/pandr/vehicle$ heroku run bash Running `bash` attached to terminal... up, run.5520 ~ $ which psql ~ $ exit exit
ありません。
じゃあ、herokuコマンドの heroku pg:psql を使えばいいかと思うと、こちらの記事(HerokuのWorker再起動問題を考える)でも書かれているように、これもdyno上にはherokuコマンドがありません(※)。
仕方ないので大昔にやったpythonで書こうかと思うと、この場合もpythonのパッケージ管理ツールのpipがdyno上に見つけられませんでした。
とまあ、こんな感じであれこれ調べてやっとたどり着いたのが上記のやり方でした。
※ 特盛!Heroku のp38あたりにあるように、dyno上でherokuコマンドを使う方法はあるようです。