読者です 読者をやめる 読者になる 読者になる

冥冥乃志

ソフトウェア開発会社でチームマネージャをしているエンジニアの雑記。アウトプットは少なめです。

follow us in feedly

SimpleTwoDoに完了済タスクを非表示にするジョブを追加しました

先日、機能追加しました。
その際に、heroku schedulerを触ってみたので、機能概要とともにまとめてみようと思います。

概要

バッチは毎日3:00(日本時間)に起動し、各ユーザのタスクから完了済のものに対して非表示を示すフラグを設定します。
また、タスクリスト画面では、非表示状態になっているタスクを表示しないように機能変更しました。

herokuの無料枠について

herokuは1アプリケーションあたり最大750時間*1が無料枠として割り当てられます。ちなみにこの時間には、Webやワーカーのプロセスだけでなくheroku runコマンドを使って実行したプロセス(herokuのドキュメント上ではone-off processと呼ばれています)の時間なども含まれるようです。
そして、Webアプリケーションのプロセスを一つアプリケーションに設定すると730時間分消費されます(バックグラウンドワーカーのプロセス一つでも同様に730時間消費します)。で、20時間ほど残るわけですが、だいたいone-off processを使って調査するというのもたかが知れているので、まずもって使い切ることはありません。この時間を使って、簡単なバッチなんか流せたりするとうれしいですよね?SimpleTwoDoの最初のリリースでは、タスクの完了/未完了に関わらず、拾ったタスクを全て表示するようにしていたので、デイリで表示の制御をしてあげることにします。

バッチプログラムの作成

まずは前述のone-off processとしてキックするバッチのプログラムを実装します。Getting Started with Scala on Heroku | Heroku Dev Centerにone-off processの作成の仕方があるので、それに従います。単純にアプリケーションとして実行可能なプログラムを書けば良いです。実際のプログラムの仕様は以下です。

  1. 全てのユーザを取得する
  2. 取得したユーザのタスクを調査
  3. 各ユーザのタスクで完了済かつ表示する設定のものを非表示に切り替える

上記仕様の実現に伴って、Salatで利用するオブジェクトを一部修正して、データベースオブジェクトから全ユーザを取得できるように関数を追加しています。
ちなみに、今回mongoDBのドキュメントにたいして項目を一つ追加している訳ですが、この辺りの項目の変更をデータベースを止めずにできるのはスキーマレスならではですね。新しい項目未対応の既存データへの対応は、マッピングするオブジェクトの該当項目に対してデフォルト値(今回はdisplayFlag = true)を設定すれば問題ありませんでした。

heroku schedulerの設定

上記のプログラムが作成できたら、次はheroku側の実行環境の設定です。下記を実行してheroku scheduler add-onをアプリケーションに設定します。

heroku addons:add scheduler:standard

これで、アプリケーションに対してadd-onが追加されているはずです。

追加されたスケジューラに対して、ジョブを設定します。このバッチはデイリで動けば十分です。また、海外の人は使わないでしょうから、使っている人が少ない時間帯ということで、午前3:00に実行するようにします。
アプリケーションの設定画面右上のAdd-onsメニューから「Heroku Scheduler」を選択して、管理画面からジョブを追加します。上記条件に従って以下の設定のタスクを追加しました。

TASK FREQUENCY NEXT RUN
target/start com.simpletwodo.batch.ChangeTaskDisplay Daily 12:00

起動時間が12:00になっているのは、herokuがUTC基準で、JST(日本時間)午前3:00に実行するために+9:00しているためです。その他の設定に関しては特に問題ないかと思います。
その他スケジューラの細かい使い方に関しては、Heroku Scheduler | Heroku Dev Centerをご覧ください。

Procfileの修正

実は、スケジューラの設定をする前に一カ所別件ではまっています。バッチプログラムを追加したプロジェクトをherokuにpushしたところ、Web側が動かなくなってしまったんですね。ログを見たところ、どうやらWebクラスが見つからないと言っているようです。

2012-04-12T14:43:19+00:00 heroku[web.1]: Starting process with command `target/start com.sample.Web`
2012-04-12T14:43:19+00:00 app[web.1]: Caused by: java.lang.ClassNotFoundException: com.sample.Web
2012-04-12T14:43:19+00:00 app[web.1]: 	at java.net.URLClassLoader$1.run(URLClassLoader.java:217)
2012-04-12T14:43:19+00:00 app[web.1]: Exception in thread "main" java.lang.NoClassDefFoundError: com/sample/Web
2012-04-12T14:43:19+00:00 app[web.1]: 	at java.security.AccessController.doPrivileged(Native Method)
2012-04-12T14:43:19+00:00 app[web.1]: 	at java.net.URLClassLoader.findClass(URLClassLoader.java:205)
2012-04-12T14:43:19+00:00 app[web.1]: 	at java.lang.ClassLoader.loadClass(ClassLoader.java:266)
2012-04-12T14:43:19+00:00 app[web.1]: 	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:294)
2012-04-12T14:43:19+00:00 app[web.1]: 	at java.lang.ClassLoader.loadClass(ClassLoader.java:321)
2012-04-12T14:43:19+00:00 app[web.1]: Could not find the main class: com.sample.Web. Program will exit.
2012-04-12T14:43:20+00:00 heroku[web.1]: Process exited with status 0

Webプロセスの実行クラス(main関数を持っていてプロセスのエントリとなるクラス)はプロジェクト内のProcfileで指定されています。その内容は以下の通りです。

web: target/start com.sample.Web

先ほどのone-off processの書き方と同じです。確かに、私が作っているWebアプリケーションのWebクラスはcom.example.Webであり、com.sample.Webではない訳なんですが、ここで一つ重要な事実を。

プロジェクトを作成して以降、Procfileを修正していません。

つまり、クラスのフルパスが違う状態で今まで動いていたということになります。どういうことでしょうかね?この原因に関してはさっぱりわからなかったものの、明らかに別のところをさしているProcfileだけは修正してpushすることで問題は解決しています。

バッチにかかる時間

で、実際にバッチの運用は開始している訳ですが、どのくらいの時間を消費するのかは気になるところです。
herokuのMy Accountメニューから各アプリケーションの利用時間と課金状況がわかるので、確認してみましょう。

2012-04-12 07:42:45 -0700 run - target/start com.simpletwodo.batch.ChangeTaskDisplay 0.012

まだ、ユーザ数もタスク量も少ないためこれをまま鵜呑みにする訳にはいきませんが、1回あたり0.012時間なので1ヶ月の運用でも十分に乗り切れそうです。ユーザ数と合わせて継続的に確認して運用の見直しは考える必要はありそうですが、この辺は追々やっていきます。少なくとも、今は問題にしていません。


ちなみに今回のソースはブログにのせていませんが、Githubには既に反映してあります。変更の詳細はComparing 56771eff66...582d3de73b · Shinsuke-Abe/Simple-Two-Do · GitHubをご覧ください。
今回、完了済タスクは非表示にしているだけで削除していません。既に画面のメニューにも出ている通り、これらを完了済タスク一覧として表示させる予定です。

*1:dyno-hoursという表現をされています。CPU時間やプロセスの時間と完全に一致する訳ではないということでしょうか。