Diary

@ssig33

25 Dec 2017 Mon 17:03

Heroku でのスケジュールタスクの実行(Rails の場合)

Heroku ではいわゆる cron 的なことをしたい人のために Heroku Schedulerというものが用意されています。

ですがこれはかなり問題の多いプロダクトで、シリアスな業務で用いるにはいろいろと厳しいです。まず設定 UI が非常に貧弱で、設定ミスをしても気付きづらいです。またセッション管理の点にも問題があり、自分がいじってるつもりと別アプリの設定をいじってしまうという事故はわりとよく起きます。

そして最大の問題ですが、公式のドキュメント

Scheduler job execution is expected but not guaranteed. Scheduler is known to occasionally (but rarely) miss the execution of scheduled jobs.

とある通り実行されるかどうかは無保証です。実行漏れは「rarely」だと書かれていますが、実感としてレアかどうかと言われると、ガチャのレアぐらいのレアさだなと思います。ユビレジの場合酷いときはまいにちなにか抜けてリカバリーを開発者が手動でやるなんてことをやってる時期がありました。

そこでドキュメントには

If scheduled jobs are a critical component of your application, it is recommended to run a custom clock process instead for more reliability, control, and visibility.

とありますのでこの行き方でやっていく必要があります。ようするに計時専門の Dyno を立ててそこで自前で cron 的なことをしろってわけ。 Rails の場合 resque-schedulerはよい選択肢です。設定がコードになってリポジトリに入れられるのも非常によい(デプロイターゲットに応じてこのあたりの設定をいじりたいとかなってくると欠点として認識されることもあるかもしれませんが)。

ところで Heroku の Dyno は最大でも 24 時間しか連続起動してくれません。また CLI から restart かけただとかデプロイしたとかで頻繁に再起動されます(ユビレジでは一日数回〜数十回のデプロイが行なわれます、最近ではわりとどこの会社もこんなかんじですよね?)。そうなるとその再起動のタイミングで resque-scheduler がなにかすべきだった!!!みたいなときにデプロイが走ったりすると実行漏れが起きます。体感ですけど heroku scheduler のときの半分ぐらいの頻度でこれらが原因となって実行漏れが起きました。

resque-scheduler はこういうことを想定して作られており

You may want to have resque-scheduler running on multiple machines for redundancy. Electing a master and failover is built in and default.

とあります。 Heroku でこれを活用したいときはわりと話は単純で

heroku scale scheduler=2

とかするだけです。ところが

Precautions are taken to prevent jobs from potentially being queued twice during failover even when the clocks of the scheduler machines are slightly out of sync (or load affects scheduled job firing time).

ともあり、 resque-scheduler を複数プロセス実行している場合に重複実行されるケースがあるようです。今のところユビレジでは重複実行されて致命的な事態となりうるジョブがあまりないためこの問題への対処は手をつけていません。ただしユビレジからのメールが二通届いてしまうなどという事態が稀に生じていることは確認されており、頻度次第ですが今後対処を行なう必要があるとは思います。

対処法としてはアプリ側でもう実行済みかどうか確認してハネるみたいなのが必要でしょう。実はユビレジでは一部のジョブでそれが導入されています。というのも Heroku Scheduler 時代に「1 時間に 1 回のタスクがたまに抜けるなら 5 分に 1 回実行して、アプリ側で重複実行だったら弾けばいいんじゃない?」みたいなのが導入されたからです(なおこれは多重実行されて困る系のタスクではなく、重いので実行されまくると困るというタイプのタスクでした)。

Heroku 公式のドキュメントにも説明あるし、いろいろとツールも揃っているのですが、なかなかこういう話を日本語で見聞したことがないので書いておきました。