Ruby Sidekiq Kubernetes

半年に一度落ちるSidekiq Jobの謎

Ruby Sidekiq Kubernetes

SUZURI事業部ウェブエンジニアのkromiiiです。SUZURIではさまざまな非同期ジョブをSidekiqを使って管理しています。その中で月に一度、月初に実行されるジョブがあり、これがなぜか半年に一回の頻度で落ちることがあったので、その原因と対策をお話しします。

  1. 発生していた現象
  2. 原因の調査
  3. 対策
    1. 1. ジョブをべき等にする
    2. 2. terminationGracePeriodSeconds を設定する
    3. 3. Sidekiq の外でジョブを実行する
  4. おわりに

発生していた現象

Sidekiqを実行しているPodのログを見たところ、ジョブは以下のようなメッセージとともに突然終了していました。

{"ts":"2025-05-01T05:06:16.936Z","pid":1,"tid":"2hd","lvl":"INFO","msg":"Bye!"}

これ以外にはエラーメッセージはなく、突然Podが停止しているような印象を受け、デバッグは難航しました。結局原因は特定できず、重いジョブが特定の時間に集中してシステムの負荷が高まったのが原因だろうと判断し、ジョブに必要なデータを再抽出した上で時間を空けて再度実行することで解決しました。

原因の調査

しかしその半年後、またしても同じようなメッセージとともにジョブが落ちることがあり、今回はログだけでなくさまざまなメトリクスを見ながら原因を調査していたところ、ある事実に気づきました。

metrics

上の画像はジョブが落ちた前後のSidekiqのCPU使用率で、それをPodごとに色分けして表示しています。これを見ると、想定したよりもCPU使用率はずっと低く、システムの負荷が原因で落ちているわけではなさそうだということがわかりました。

しかし色分けの結果からジョブが落ちた前後でなぜかPodが生え変わっていることに気づきました。「なぜこのタイミングでPodが生え変わったのだろう」と考えると、思い当たることが一つありました。本番リリースです。

リリースによってSidekiqのジョブが消し飛んでしまう現象は有名なこちらのスライドでも説明されていましたが、まさにそれと似たような現象がわれわれのサービスにも起こっていました。つまりジョブが実行される時間に本番リリースがあり、これによってSidekiqのPodが生え変わってしまうと、実行中のジョブが中断されてしまっていたのです。

わかってしまえば簡単な問題ではありますが、このジョブが月一回しか実行されないことに加え、実行時間がちょうどエンジニアの昼休憩の時間と重なっていることがこの問題の発見を難しくしていました。つまりこのジョブが実行される月初に早めの昼休憩をした人がいて、その人がジョブの実行中に本番リリースすると失敗する仕様になっていたのです。

対策

この問題に対する対策として以下の三つを考えました。

1. ジョブをべき等にする

Sidekiqは上記のような理由で終了した場合、中断されたジョブを再度キューに積み直し、再起動後にリトライしてくれます。そのため本来ジョブがべき等にできていれば(再実行に耐えうる設計になっていれば)問題なく動いた可能性が高いです。今回のような問題に対してはまずはジョブの処理をべき等に書き直すのが最善と考えます。しかし問題になったジョブは会計に関連するジョブであり、ロジックの変更による影響が大きいと判断し、今回は見送りました。

2. terminationGracePeriodSeconds を設定する

これは社内のパートナーから教えてもらったのですが、Sidekiqのジョブ側で対応できない場合はKubernetesのマニフェスト側で対応する方法もあります。Sidekiqのドキュメントによれば、KubernetesのマニフェストにterminationGracePeriodSeconds を設定すると、 Pod に SIGTERM が送られてもコンテナはすぐに終了せず、terminationGracePeriodSecondsで設定した時間が経過するまでジョブの終了を待つことができるとありました。

この方法を使えばジョブにかかる時間を見積もり、余裕のある時間を設定することで解決できると考えましたが、月に一度しか実行されないジョブのために全体の待ち時間を伸ばすことになるのと、待ち時間を伸ばすだけだと根本解決にはならないと判断し、最終的には次の三つ目の方法を採用しました。

3. Sidekiq の外でジョブを実行する

これはSidekiqのPodが落ちるのが原因であれば、Sidekiqの外でジョブを実行してしまおうという発想です。具体的には弊社の技術基盤チームが作ってくれた oneshot-jobという枠組みを使って、ジョブ専用のPodを立てることで問題を解決しました。

oneshot-jobは指定されたコマンドをKubernetes Jobマニフェストに変換するコマンドラインツールで、ここに bundle exec rails runner を指定することで任意のジョブをKubernetesのjobとして動かすことができます。

手動で実行するという手間はかかりますが、もともと月に一回しか実行しないジョブなのと、これによってリリースに巻き込まれてジョブが終了することが確実になくなるというメリットがあるため、最終的にエンジニア内で合意が得られました。

おわりに

今回は半年に一度落ちるSidekiqジョブの原因を調査し、改善した事例を紹介しました。これにより安心して早めのお昼が食べられるようになったということでエンジニア一同非常に喜んでいます。