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

Sidekiqを安全に再起動(終了)したい

Ruby Rails

バージョン

  • Sidekiq 3.3.0

基本動作

ジョブが走っている状態でSidekiqを終了すると、8秒間(デフォルト)待って終了する。そして、8秒以内に終了しなかったジョブはキューに戻される。

メール送信くらいの軽いジョブであればで問題にはならないが、例えば、AWSAPI叩いてRDSのインスタンスを作って完了まで待つジョブなんて走らせると平気で20分以上かかってしまう。しかもキューに戻されると再起動後にもう一台インスタンスが作成されてしまうのでかなり問題だ。

では安全に再起動するにはどうすればいいのか

Sidekiqのプロセスに対してまずはSIGUSR1を送り、その後にSIGTERMを送ればいい。

SidekiqはUSR1を受け取ると、実行中のジョブはそのまま継続し、新規ジョブの実行を停止する。(キューは引き続き溜まる) 実行中のジョブが0になったタイミングでTERMを送って終了し、そして再度起動すれば、リトライも発生せず安全に再起動できる。

シグナルを送るのはkillコマンドでも可能だが、sidekiqctlコマンドを使う方が簡単。(sidekiq/webのQuiet/Stopボタンも同じ)

$ sidekiqctl quiet [pidfile]          # SIGUSR1を送信
$ sidekiqctl stop [pidfile] [timeout] # SIGTERMを送信 (timeout後はSIGKILLが送信される)

ただ一つ問題があって、sidekiqctl stopは、実行中のジョブが0になったかは保証してくれない。かなり長めにtimeoutを設定するという手はあるが、確実にジョブの完了を待たないといけないケースでは、以下のようなコードを書く必要がある。

Sidekiqプロセスは1つの想定

require 'sidekiq/api'

namespace :sidekiq do
  task 'stop' do
    ps = Sidekiq::ProcessSet.new
    abort 'Sidekiq process not running' if ps.count == 0

    ps.first.quiet!

    puts "SIGUSR1 sent\nWaiting 10sec for status update"
    sleep 10

    while (running_tasks = ps.first['busy']) > 0
      puts "Waiting for tasks to finish. Num tasks: #{running_tasks}"
      sleep 5
    end

    ps.first.stop!
    puts 'SIGTERM sent'
  end
end

プロセスが自身の状態(state)をRedisへ送信する間隔が5秒のため、#quiet!を実行した後にsleepを5秒以上挟むのがポイント。

プロセスではなくキュー単位で細かく制御したい場合は、有料のsidekiq/pro/api#pause!sidekiq-limit_fetch#pauseを使うといいかもしれない。

参考