sidekiq-cronアップデートへの道のり

f:id:yu_suke1994:20181121141855p:plain

こんにちは。ジョブスケジューラーとしてはSidekiqしか触れてこなかった うなすけ (id:yu_suke1994) です。

さて、Railsアプリ開発者の皆さんは定期的な bundle update を何らかの方法で実行していることでしょう。弊社でも最近になって Dependabot を導入することになりました。

今回は、Dependabot を導入する前に、一気に bundle update したときに起こった Sidekiq まわりの問題、それも sidekiq-cron で起こった問題について書いていこうと思います。

「一気に bundle update」 とは何か

弊社サービス、特に今回はCASHのAPI サーバーのRailsについての話になりますが、これまでは気付きベースで bundle update を行なってきました。

さすがに自動でやっていく仕組みを入れたいので、ツール導入のためにもう一度 bundle update を実行し、今後はツールによる差分のみを注意して見ればいいという状況に持っていくことにしました。1

問題なく bundle update できた……と思っていた

f:id:yu_suke1994:20181120171335p:plain

updateされるgemのコードを確認し、staging環境での動作確認も行ない、特にエラーもなかったのでmergeしてdeployしたのですが、本番ではJobの実行時に例外が発生するようになってしまいました。

なぜCronJobは実行されなかったのか

sidekiq-cron は、CronJob が最後にenqueueされた時刻をRedisに保存します。 v0.6.4 でのコードは以下のようになっています。

#enque cron job to queue
def enque! time = Time.now.utc
  @last_enqueue_time = time

https://github.com/ondrejbartas/sidekiq-cron/blob/v0.6.3/lib/sidekiq/cron/job.rb#L47-L74

ここで、 Time.now.utc の結果は、このまま to_s されてRedisに保存されます。

さて、 Redisから最後に queue に入った時刻を取り出す部分の v1.0.4 でのコードが以下のようになっています。

def parse_enqueue_time(timestamp)
  DateTime.strptime(timestamp, LAST_ENQUEUE_TIME_FORMAT).to_time.utc
rescue ArgumentError
  DateTime.strptime(timestamp, LAST_ENQUEUE_TIME_FORMAT_OLD).to_time.utc
end

https://github.com/ondrejbartas/sidekiq-cron/blob/v1.0.4/lib/sidekiq/cron/job.rb#L554-L558

そして、 LAST_ENQUEUE_TIME_FORMAT_OLDLAST_ENQUEUE_TIME_FORMAT は次のようになっています。

LAST_ENQUEUE_TIME_FORMAT = '%Y-%m-%d %H:%M:%S %z'
LAST_ENQUEUE_TIME_FORMAT_OLD = '%Y-%m-%d %H:%M:%S'

https://github.com/ondrejbartas/sidekiq-cron/blob/v1.0.4/lib/sidekiq/cron/job.rb#L15-L16

この辺で勘のいい方は気づかれるかと思いますが、Redisからの last_enqueue_time をparseする際、書式が想定外だと例外が上がります。

つまり、 Time::DATE_FORMATS[:default] を独自定義していた場合、 sidekiq-cron v0.6.4 時代に実行されたCronJobは v1.0.4 にアップデートすると実行できずに例外が上がってきます。

そして案の定、CASHのAPIサーバーでは Time::DATE_FORMATS[:default] が再定義されており、前述の挙動によって sidekiq-cron v1.0.4 が動作しませんでした。

アップデートするためにやること

このとき、無事に v1.0.4 にアップデートするために取り得る手段として、次の3つが候補としてありました。

  1. 正しい書式の時刻をRedisに格納し直す
    • 独自定義した書式からは時刻単位の情報が欠落しているので復元が不可能
    • Redisの値を手で操作したことがなく、不安
  2. sidekiq-cronで使用している key-value の組を全部削除する
    • これまでの実行情報が全て消えてしまう(が、そもそも正確ではないし……)
  3. 独自定義を削除し、正しい書式でRedisに格納されるまで待つ
    • 時間はかかるが安全

ということで、「独自定義を削除し、正しい書式でRedisに格納されるまで待つ」ことにしました。CronJobのなかには、毎月1回実行されるというものがあります。なので、 Time::DATE_FORMATS[:default] の設定を削除してから1ヶ月待ち、Redisに格納されている last_enqueued_at の書式が全て正しいものになっているかどうかを確認したうえで、sidekiq-cronのアップデートを行いました。

まとめ

という訳で、継続的bundle updateの体制が整いました!流れでRailsのバージョンも5.2.1になりました。

引き続き、やっていきましょう。


  1. 今考えると、そのようなことをする必要はなかったんじゃないかと思いますが……