死活監視ツールGodが暴走するときの対処方法


## 結論

DelayedJobのような長時間実行するプロセスに対するGod設定には`grace`を設定しましょう。

```ruby
God.watch do |w|
  ...
  w.grace = 5.seconds
  ...
end
```

## Godとは

Godとはプロセスの死活監視ツールの一つです。プロセスが死ねば再び命を与え、プロセスがコンピューティングリソースを浪費するような悪さをすれば罰を与えるという、まさに『神』の如きツールです。

Rubyで作られたツールであり、設定ファイルもRubyによるDSLを用いて柔軟に構成することができます。
下記は、いろいろなサイトで紹介されているよくある設定です。

```ruby
RAILS_ROOT = "/path/to/rails_root"
RAILS_ENV  = "production"

3.times do |num|
  God.watch do |w|
    w.name = "delayed_job-#{num}"
    w.pid_file = "#{RAILS_ROOT}/tmp/pids/delayed_job.#{num}.pid"
    w.start = "cd #{RAILS_ROOT} && script/delayed_job --environment=#{RAILS_ENV} --identifier=#{num} start"
    w.restart = "kill -TERM `cat #{RAILS_ROOT}/tmp/pids/delayed_job.#{num}.pid`"
    w.stop = "cd #{RAILS_ROOT} && script/delayed_job --environment=#{RAILS_ENV} --identifier=#{num} stop"
    w.log = File.join(RAILS_ROOT, "log/god_delayed_monitor.#{num}.log")
    w.behavior(:clean_pid_file)

    w.start_if do |start|
      start.condition(:process_running) do |c|
        c.interval = 5.seconds
        c.running = false
      end
    end

    w.restart_if do |restart|
      restart.condition(:memory_usage) do |c|
        c.above = 500.megabytes
        c.interval = 10.seconds
      end
    end
  end
end
```

この設定によって以下のような挙動となります。

* delayed_jobプロセスを3つ稼働させる
* 5秒ごとにプロセスの存在をチェックし、存在していなければ起動させる
* 10秒ごとにプロセスの物理メモリ使用量をチェックし500MBを超過したら再起動させる
	* 再起動には`kill`を使いプロセスにTERMシグナルを送信する

## こまりごと

前述の設定で死活監視としてはきちんと動作します。しかし、監視対象がDelayedJobの場合に困った事象が発生します。

DelayedJobはTERMシグナルを受け取ったときにジョブ実行中の場合、割り込み処理で内部のフラグを立てたのち元のジョブ処理に戻ります。そしてジョブ実行が完了した後にプロセスを終了するという、gracefulな終了をしてくれます。

一方Godは、監視対象プロセスがメモリ超過したことを検知し、TERMシグナルを送信しますが、DelayedJobプロセスはすぐには死なない。なので再びTERMシグナルを送信します。が、やっぱり死なない... このときのGodの挙動がヒドイ。鬼神の如くこれでもかってくらい殺しにかかる。

```text:delayed_job.log
2015-09-18T18:40:15+0900: [Worker(delayed_job.1 host:BG.EXAMPLE.COM pid:31039)] Exiting...
2015-09-18T18:40:15+0900: [Worker(delayed_job.1 host:BG.EXAMPLE.COM pid:31039)] Exiting...
2015-09-18T18:40:15+0900: [Worker(delayed_job.1 host:BG.EXAMPLE.COM pid:31039)] Exiting...
2015-09-18T18:40:15+0900: [Worker(delayed_job.1 host:BG.EXAMPLE.COM pid:31039)] Exiting...
2015-09-18T18:40:15+0900: [Worker(delayed_job.1 host:BG.EXAMPLE.COM pid:31039)] Exiting...
2015-09-18T18:40:15+0900: [Worker(delayed_job.1 host:BG.EXAMPLE.COM pid:31039)] Exiting...
2015-09-18T18:40:15+0900: [Worker(delayed_job.1 host:BG.EXAMPLE.COM pid:31039)] Exiting...
2015-09-18T18:40:15+0900: [Worker(delayed_job.1 host:BG.EXAMPLE.COM pid:31039)] Exiting...
2015-09-18T18:40:15+0900: [Worker(delayed_job.1 host:BG.EXAMPLE.COM pid:31039)] Exiting...
2015-09-18T18:40:15+0900: [Worker(delayed_job.1 host:BG.EXAMPLE.COM pid:31039)] Exiting...
2015-09-18T18:40:15+0900: [Worker(delayed_job.1 host:BG.EXAMPLE.COM pid:31039)] Exiting...
2015-09-18T18:40:15+0900: [Worker(delayed_job.1 host:BG.EXAMPLE.COM pid:31039)] Exiting...
2015-09-18T18:40:16+0900: [Worker(delayed_job.1 host:BG.EXAMPLE.COM pid:31039)] Exiting...
2015-09-18T18:40:16+0900: [Worker(delayed_job.1 host:BG.EXAMPLE.COM pid:31039)] Exiting...
2015-09-18T18:40:16+0900: [Worker(delayed_job.1 host:BG.EXAMPLE.COM pid:31039)] Exiting...
2015-09-18T18:40:16+0900: [Worker(delayed_job.1 host:BG.EXAMPLE.COM pid:31039)] Exiting...
  ...
```

その数、1秒間に約12回。TERMシグナルを送るためにbashプロセスとkillプロセスがそれぞれ生成されるし、シグナル受け取った側はその度に割り込み処理と復帰処理が発生。ジョブが1時間くらいかかるものだった場合、その間ずっとこれらの処理が繰り返されます。おかげで、サーバーのCPU負荷が上がりまくり。

我々は神の怒りをかってしまった。この世に救いはないのか...

## 神の怒りを沈める方法

Godのソース(ver. 0.13.6)を眺めていたら下記の記述がありました。

```ruby:Watch#action
def action(a, c = nil)
  ...
  case a
  when :start
    call_action(c, :start)
    sleep(self.start_grace + self.grace)       # ← ん?
  when :restart
    if self.restart
      call_action(c, :restart)
    else
      action(:stop, c)
      action(:start, c)
    end
    sleep(self.restart_grace + self.grace)     # ← んん!?
  when :stop
    call_action(c, :stop)
    sleep(self.stop_grace + self.grace)        # ← おおぉぉ!!!?
  end
  self
end
```

(ﾉﾟρﾟ)ﾉ ｫｫｫ･･ｫ･･ｫ････ 神は我らに慈悲を与え給うた...

Godの`watch`ブロックには`grace`および`start_grace`, `restart_grace`, `stop_grace`が用意されていました。これを設定すると再起動等のアクションが実行された後、通常の監視状態に戻る前に指定時間だけsleepしてくれます。

```ruby
God.watch do |w|
  ...
  w.grace = 5.seconds
  ...
end
```

この設定をいれることによって、穏やかに再起動が実行されるようになりました。めでたし。

## まとめ

GodでDelayedJobを監視したときに再起動処理が短時間に何度も繰り返される挙動を緩和する方法を紹介しました。

余談ですが、死活監視にGodというメタファーを用い、ウェイト時間にgrace(慈悲、寵愛)という名前を付けるセンスが羨ましい...