🦔

Sidekiq のジョブが途中で止まる!?Sidekiq の Shutdown を正しく理解して運用しよう

に公開

はじめに

普段の業務で Sidekiq を使っていると、どうしても避けられないのが「デプロイ時の再起動」ですよね。
そのときに意外と見落としがちなのが、Sidekiq の Graceful Shutdown(優しいシャットダウン) の仕組みです。
これをちゃんと理解していないと、せっかく実行しようとしたジョブがキューに戻されて実行されないという、ちょっと怖いことが起こります。
今回はその仕組みと、運用上で気をつけるべきポイントをまとめました。

Sidekiq の Graceful Shutdown って何?

まず、Sidekiq ではサーバーのシャットダウン時に「いきなりブチッと終了」しないように、Graceful Shutdown という仕組みが用意されています。
これは、たとえばサーバーやコンテナの再起動時に、現在動いているジョブをなるべく最後まで実行しきってから終了するための仕組みです。

デフォルトの挙動

Sidekiq は SIGTERM という終了シグナルを受け取ると、以下のように動きます

  • 新しいジョブの実行を止める
  • 25秒間(デフォルト秒数)だけ待つ
  • 25秒経っても終わってないジョブは止めて、キューに戻す
    つまり、25秒以内に終わらないジョブは途中で止められる可能性があるんです。

ちなみに秒数は sidekiq.yml やコマンドラインオプションで設定できる timeout の値によって変えられます。
https://github.com/sidekiq/sidekiq/wiki/Signals#term

実際の運用で起きること(ECSの場合)

たとえば AWS ECS 上で Sidekiq を動かしているケースを考えてみましょう。
ECS ではコンテナを停止するとき、まず SIGTERM を送ってきてから、30秒間だけ待機します。
その間にプロセスが終了しなければ、容赦なく SIGKILL(強制終了)が飛んできます。
https://aws.amazon.com/jp/blogs/news/graceful-shutdowns-with-ecs/

Sidekiq 側では SIGTERM を受け取ると、新しいジョブの受け付けを止め、設定された timeout(デフォルト25秒)だけ待ってくれます。
ただし、25秒以内にジョブが終わらなければ、強制的にジョブを中断してキューに戻すので、ジョブが途中で止まってしまう可能性があります。

ざっくりしたシーケンス

どういうときに問題になるの?

  • ジョブの実行時間が長い
  • デプロイ頻度が高い
    この2つが重なると、以下のような問題が起こります。
    「ジョブが実行されたと思ったら、途中で止まって、また戻されて、また止まって……いつまで経っても終わらない!」
    重要度が高く、即時性が求められるジョブでこれが起こると問題ですよね。

対策いろいろ

じゃあ、どうやってこの問題を防げばいいのか。よくある4つの対策をご紹介します。

ジョブが終わるのを待ってから再起動する

まずは Sidekiq のジョブがすべて終わってから再起動する方法です。
以下の記事で紹介されているように、SIGTERMではなく TSTP シグナルを送れば、新規ジョブの受付のみ止めることができます。
これであれば、強制的にジョブを中断してキューに戻すという挙動が発生することはありません。
その後、現在のジョブが全て完了したことを確認してからデプロイを進めれば、ジョブが途中で止まることはありません。

https://n8.hatenablog.com/entry/2015/05/01/100424

※ 上記の記事では USR1 で紹介されていますが、Sidekiq 5.0 から TSTP に変更されました。https://github.com/sidekiq/sidekiq/wiki/Signals#tstp

ちなみに Sidekiq の Web UI 上からも、TSTP シグナルを送ることができます。
https://github.com/sidekiq/sidekiq/blob/f126015758379e4520f067d96b98e8ead38bf4ca/web/views/busy.erb#L98

タイムアウト時間を延ばす

Sidekiq の timeout を長く設定すれば、ジョブを最後まで完了できる可能性が上がります。
ただしこれは ECS 側の制約(最大120秒)もあるため、無限に延ばせるわけではありません。
また、長く設定するとデプロイ時間も長くなるので、運用とバランスを見て調整が必要です。

ジョブを分割する

そもそもジョブの処理時間が長すぎる場合は、小さい単位に分けて処理する方法も考えられます。
とはいえ、トランザクションをまたぐ複雑な処理だと、単純に分けるのが難しいケースもありますね。

長いジョブが動いている時間帯はデプロイを避ける

これは完全に運用回避策ですが、「この時間帯は重要なジョブが走ってるからデプロイしない」といった工夫も有効です。
ただし、他のリリースにも影響する可能性があるので、そこは注意が必要です。

まとめ

Sidekiq の Graceful Shutdown をきちんと理解していないと「ジョブが終わらない」「何度もやり直しになる」といったトラブルに見舞われる可能性があります。
特に デプロイ頻度が多く、ジョブの処理時間が長く、ジョブの重要度が高いような場合は要注意です。
運用に合わせた shutdown の設計をして、大事なジョブを途中で止めないように気をつけましょう!

Discussion