Sidekiqを本番環境にデプロイする [翻訳]

# Sidekiqを本番環境にデプロイする

最終更新 2019/09/04 編集者 Mike Perham

## 概要

Sidekiqを安全に停止するには、デプロイプロセスの **できるだけ早い** 段階でTSTPシグナルを送信し、**できるだけ遅く** TERMシグナルを送信する必要があります。
TSTPはSidekiqに、新しい作業をキューから取り出すことを停止し、現在の作業をすべて終了するよう指示します。
TERMはSidekiqにN秒以内に終了するように指示します。Nは` -t` タイムアウトオプションで設定され、デフォルトは25です。
TSTPとTERMをデプロイプロセス中に使用することで、停止するまでの時間を最大限ジョブに与えることになります。

タイムアウトになったときにまだジョブが実行されている場合、SidekiqはそれらのジョブをRedisにプッシュして、後で再実行できるようにします。

[![デプロイ](http://img.youtube.com/vi/50nm6pY2gwg/0.jpg)](https://www.youtube.com/watch?v=50nm6pY2gwg&list=PLjeHh2LSCFrWGT5uVjUuFKAcrcj5kSai1)


## ネットワークアーキテクチャ

プロダクション環境のサーバーでは、アプリケーションサーバー毎に1つ以上のSidekiqを実行することをおすすめします。
以前の職場では、3つのアプリケーションサーバーそれぞれに対してSidekiqプロセスを2つ起動しており、合計で6つのSidekiqプロセスを起動していました。
デフォルトのコンカレンシーは25なので、これで合計150のワーカースレッドが得られたことになります。
6つのプロセスすべてが同じRedisサーバーと通信し、`high`、`default`、`low` のキューを使用して、キュー管理とジョブの優先度を可能な限りシンプルに保ちました。

Sidekiq用に個別のサーバーを用意したり、様々なキューを使用したりすると、必要のない複雑さが増してしまいます。

## Heroku

sidekiqをHerokuで使用するのは簡単です。`Procfile` をRailsアプリケーションに追加して、Sidekiqワーカープロセスを起動します。

```r
web: bundle exec puma ...
worker: bundle exec sidekiq -t 25 ...
```

Redisアドオンに接続するには、環境変数 REDIS_PROVIDER を使って、Redisに接続するための値が入った環境変数の名前をSidekiqに指定してください。例：`heroku config:set REDIS_PROVIDER=REDISTOGO_URL`
[Railsキャッシュストア](https://guides.rubyonrails.org/caching_with_rails.html#activesupport-cache-rediscachestore)などの他の用途とは[別のRedisインスタンス](https://github.com/mperham/sidekiq/wiki/Using-Redis#multiple-redis-instances)を使うようにしてください。

Herokuはプロセスの再起動に30秒のハードリミットを設定していることを覚えておいてください。`-t 25` はSidekiqに強制シャットダウンを開始する前にジョブの終了を25秒間待つよう指示します。
TSTPシグナルを送信することはできませんが、オプションで、デプロイ前のフックで `Sidekiq::ProcessSet.new.each(&:quiet!)` を使用して、ジョブにさらに長い終了までの時間を与えることができます。
25秒が経過すると、Sidekiqは未完了のジョブをRedisに戻します。 ジョブが[冪等](https://github.com/mperham/sidekiq/wiki/Best-Practices)であることを確認して、プロセスの起動時にジョブを再試行できるようにしてください。

Procfiles、Foreman、Herokuの詳細については[このページ](https://devcenter.heroku.com/articles/procfile)を参照してください。

Sidekiqのメモリ使用量を大幅に削減するために下記の環境変数を設定することを強くおすすめします。

```shell
heroku config:set MALLOC_ARENA_MAX=2
```

## 独自のサーバーでプロセスを起動する

独自のサーバーでSidekiqを実行する場合は、UpstartまたはSystemdを使用して、Sidekiqをシステムサービスとして起動します。 これにより、Sidekiqがクラッシュした場合にプロセスが確実に再起動されます。

Sidekiqインスタンスの管理に使用する[Upstart構成の例はこちら](https://github.com/mperham/sidekiq/tree/master/examples/upstart)です。
これらのファイルを `/etc/init` に置くことで、マシンの起動時にSidekiqが自動的に起動されるようになります。
Sidekiqプロセスグループは `[start | stop | restart] workers` を使って非常にシンプルに管理できます。

[systemd構成の例はこちら](https://github.com/mperham/sidekiq/tree/master/examples/systemd)です。
systemdのドキュメントには、[.serviceファイル](http://0pointer.de/public/systemd-man/systemd.service.html)についてのセクションと[プロセスの実行](http://0pointer.de/public/systemd-man/systemd.exec.html)についてのセクションがあります。
**Linuxを使用する開発者であれば、これらのドキュメントは何度も読んでおきましょう。**

Sidekiq Enterpriseの [sidekiqswarm](https://github.com/mperham/sidekiq/wiki/Ent-Multi-Process) バイナリを使えば、単一のUpstart/SystemdサービスでN個のSidekiqプロセスを簡単に起動できます。
開発環境や本番環境でアプリケーションプロセスの自動化を便利に行うツールとして、[Foreman](https://github.com/ddollar/foreman)も参照してみてください。

## Capistranoを使用してデプロイする

[capistrano-sidekiq gem](https://github.com/seuros/capistrano-sidekiq)を使用してください。以前はあった統合機能は削除されています。
デフォルトでCapistrano自体がデーモン化まで行うため、Sidekiqプロセスがクラッシュした場合に自動的な再起動はされない点に注意してください。

## Sidekiqのデプロイに伴うイベント

Sidekiqプロセスは、起動するときと終了するときにライフサイクルイベントを呼び出します。

```ruby
Sidekiq.configure_server do |config|
  # アプリケーションが初期化されたすぐ後に実行されます。このときはまだジョブは実行されていません。
  config.on(:startup) do
    make_some_singleton
  end

  config.on(:quiet) do
    puts "Got TSTP, stopping further job processing..."
  end

  config.on(:shutdown) do
    puts "Got TERM, shutting down process..."
    stop_the_world
  end
end
```

上記の仕組みは、独自のスレッド/アクターを開始/停止したい場合やシグナルを利用したい場合に非常に便利です。

Sidekiq Proのユーザーであれば、`:quiet` を使用することで、再起動の前に長時間動き続けるジョブに対して通知を行うことができます。（super_fetch の有効化が必要）

```ruby
# config/initializers/sidekiq.rb
$shutdown_pending = false
Sidekiq.configure_server do |config|
  config.on(:quiet) do
    $shutdown_pending = true
  end
end

# some_job.rb
def perform
  # このジョブは数千のアイテムを処理し、1時間かかる場合があります。
  # イテレーションの各回でシャットダウンをチェックします。
  # big_list_of_items は未処理のアイテムのみを返すので、
  # 残りのアイテムから処理を再開することができます。
  big_list_of_items.find_each do |item|
    process(item)
    # Sidekiq Proはプロセスが再起動するとすぐにジョブを再試行します。
    raise Sidekiq::Shutdown if $shutdown_pending
  end
end
```