unincorn-worker-killerを試してみた

## 背景
Webアプリで負荷が高い処理を実施した時にサーバのメモリ使用量が急激に増えたまま減っていかない現象を確認しました。
unicornプロセスのメモリ使用量が高い事を確認。unicornに着目し対策を取ることにいたしました。
そもそもunicorn自体にはプロセスのリサイクル機能がなく、メモリ、swap領域が枯渇しmysqlやunicornのプロセスが殺されたり、設定次第ではサーバ自体へアクセスできない現象が起きる可能性があるとのこと。
unicornのプロセスを監視し、落ちていれば起動するという設定はしているが、落ちる前に対策できないものか。
偉大なるインターネット様に尋ねてみたところ、unicorn-worker-killerというgemが存在する事を知りました。
実際にテスト環境で試してみて対策となるかどうかを検討していきます。

## 導入・検証
ローカル環境での検証を開始。
なんとこのgemの生みの親は日本人との事。
ただ、しばらくgemが更新されていないのが気になりますが・・・・

いつものようにgemをインストール。
<pre>
gem 'unicorn-worker-killer'
bundle install
</pre>

config.ruファイルへ下記を追加。

```ruby:config.ru
if ENV['RACK_ENV'] == 'production'
  # Unicorn self-process killer
  require 'unicorn/worker_killer'

　　　　max_request_min =  ENV['UNICORN_MAX_REQUEST_MIN']&.to_i || 3072
  max_request_max =  ENV['UNICORN_MAX_REQUEST_MAX']&.to_i || 4096

  # Max requests per worker
  use Unicorn::WorkerKiller::MaxRequests, max_request_min, max_request_max

  # テスト的に100MB～200MBに設定。この間でランダムに値を取得し、それを上限値としているっぽい。
  oom_min = ((ENV['UNICORN_OOM_MIN']&.to_i || 100) * (1024**2))
  oom_max = ((ENV['UNICORN_OOM_MAX']&.to_i || 200) * (1024**2)) 

  # Max memory size (RSS) per worker
  use Unicorn::WorkerKiller::Oom, oom_min, oom_max, 2 # この2はcheck_cycleを指定している。指定しない場合は16が適用される。
end
```

公式WikiにThe memory size check is done in every check_cycle requests.と書かれているため
短いスパンでのチェックを行いたい場合はcheck_cycleを少なく指定するとよさそうです。
<br>
実際にメモリを消費する処理を続けてみて、unicorn-worker-killerを強制的に発動させてみました。

```unicorn_error.log
W, [#8280]  WARN -- : #<Unicorn::HttpServer:0x007fc691746ad0>: worker (pid: 8280) exceeds memory limit (150974464.0 bytes > 106124256 bytes)
W, [#8280]  WARN -- : Unicorn::WorkerKiller send SIGQUIT (pid: 8280) alive: 111 sec (trial 1)
I, [#8268]  INFO -- : reaped #<Process::Status: pid 8280 exit 0> worker=3
I, [#8268]  INFO -- : worker=3 spawning...
I, [#8307]  INFO -- : worker=3 spawned pid=8307
I, [#8307]  INFO -- : worker=3 ready
```

ちなみに、reapは「刈る」、spawnは「生む」を意味しています。
プロセスとメモリを確認。

``` before.
[hogehoge@localhost hoge]$ ps aux | grep unicor[n]
500       8268  3.2  5.5 373332 113640 ?       Sl   14:23   0:03 unicorn master -c config/unicorn.rb -E production -D                                                                      
500       8271  1.0  5.9 391116 122804 ?       Sl   14:23   0:01 unicorn worker[0] -c config/unicorn.rb -E production -D                                                                   
500       8274  1.1  6.0 390696 124948 ?       Sl   14:23   0:01 unicorn worker[1] -c config/unicorn.rb -E production -D                                                                   
500       8277  2.8  7.3 420484 151692 ?       Sl   14:23   0:03 unicorn worker[2] -c config/unicorn.rb -E production -D                                                                   
500       8280  2.5  7.1 412688 147428 ?       Sl   14:23   0:02 unicorn worker[3] -c config/unicorn.rb -E production -D
```

``` after.                                                                   
[hogehoge@localhost hoge]$ ps aux | grep unicor[n]
500       8268  3.1  5.5 373332 113648 ?       Sl   14:23   0:03 unicorn master -c config/unicorn.rb -E production -D                                                                      
500       8271  1.0  5.9 391116 122804 ?       Sl   14:23   0:01 unicorn worker[0] -c config/unicorn.rb -E production -D                                                                   
500       8274  1.0  6.0 390696 124948 ?       Sl   14:23   0:01 unicorn worker[1] -c config/unicorn.rb -E production -D                                                                   
500       8277  3.2  7.4 421464 153416 ?       Sl   14:23   0:03 unicorn worker[2] -c config/unicorn.rb -E production -D                                                                   
500       8307  9.3  5.4 374660 111040 ?       Sl   14:25   0:00 unicorn worker[3] -c config/unicorn.rb -E production -D
```

pid:8280がKILLされて新たに8307が生まれている事を確認できた。

## 補足
- なお、unicorn-worker-killerのログはconfig/unicorn.rbの中でstderr_pathに指定されているログへ出力されます。
肥大化する事も考えられるためログローテート設定をする必要がありそうです。
- GitLabでもunicorn-worker-killerがつかわれている事が判明。
信頼性一気にアップ。<br>
https://github.com/gitlabhq/gitlabhq/blob/master/config.ru
- workerを落とす命令としてはQUIT（graceful shutdown）が送信されています。
そして次のメモリチェック時でもまた同じPIDのworkerが落ちていない場合は再度QUITが送信されます。
これが10回続いてもまだ生きていたらTERMコマンドに切り替えるようです。
続いて、TERMコマンドが5回続いても生きている場合はKILLコマンドになるみたいです。
このコマンドを切り替える回数を変更したい場合はgemの中のconfigファイルで変更する必要がありそうです。

## 所感
実際にgemのコードをのぞいてみましたが、シンプルなものとなっており好感を持ちました。unicornがメモリを大量に保持してしまった場合のメモリ解放に有効的だと判断いたしまして、最適な設定を見つけて本番環境へ是非とも導入したいgemです。

## 参考サイト
https://qiita.com/k-shimoji/items/9d49e76dae2a97208c9c
https://qiita.com/Oakbow/items/ef88df07234866c7be30
https://qiita.com/dainghiavotinh/items/8158213207b257670ff3
https://zephiransas.github.io/blog/2015/07/29/unicorn-worker-killer/

gem 公式サイト(GitHub)
https://github.com/kzk/unicorn-worker-killer