Sidekiq 6の新機能・変更点

## はじめに

Ruby on Rails (以下Rails) でバックグラウンドジョブを実行する際によく使用されるGemである、Sidekiqのバージョン6.0が今年（2019年）9月にリリースされました。このバージョンでは、Active Jobへのオプション指定などの機能強化と共に、パフォーマンス向上が行われ、`sidekiqctl`コマンドが廃止されるなどの大きな変更が行われています。本記事では実際に使用した経験を元に、Sidekiq 6の新機能や変更点について説明を行います。

## 注意点

### 本記事で取り扱うエディション

本記事ではGitHubで[公開しているOSS版](https://github.com/mperham/sidekiq)のSidekiqについて記述しています。Pro / Enterprise版を使用している場合は、[公式サイト（英語）](https://sidekiq.org/)を参考にしてください。

### サポートされるRails / Ruby / Redisのバージョン

サポートされるバージョンは以下の通りになります。

* Ruby on Rails 5以上
* Ruby 2.5以上
* Redis 4以上

上記より古いバージョンを使用している場合は、アップグレードを事前に行う必要があります。

## 新機能

### Active JobでSidekiq独自のオプションを指定可能に

`ActiveJob::Base`を継承したジョブで使用する際に、`sidekiq_options`を指定することで、Sidekiq独自のオプションを一部使用できるようになりました。

以下はリトライの回数と、Web UIで表示するバックトレースの行数を`sidekiq_options`で指定しています。

```ruby
class MyJob < ActiveJob::Base
  queue_as :myqueue
  sidekiq_options retry: 10, backtrace: 20
  def perform(...)
  end
end
```

以下が使用できるオプションの一覧となります。

| オプション  | 値                | 内容                                                         |
| ----------- | ----------------- | ------------------------------------------------------------ |
| `retry`     | 整数              | リトライ回数を指定する                                       |
| `backtrace` | true, false, 整数 | SidekiqのWeb UIにバックトレースを表示するかどうか<br />整数の場合は指定した行数で表示する |

**参考**

* [Allow Sidekiq retry options in ActiveJob by pirj · Pull Request #4213 · mperham/sidekiq](https://github.com/mperham/sidekiq/pull/4213)



### ログの出力形式を選択可能に

ログの出力機能がプラグイン化され、出力形式を選択できるようになりました。例えば、JSON形式の出力を行う場合は、以下のように`Sidekiq::Logger::Formatters::JSON`を指定します。

```ruby
Sidekiq.configure_server do |config|
  config.log_formatter = Sidekiq::Logger::Formatters::JSON.new
end
```

以下が使用できるログの出力形式の一覧となります。

| フォーマッタ                                    | 説明                     |
| ----------------------------------------------- | ------------------------ |
| `Sidekiq::Logger::Formatters::Pretty`           | 通常の出力               |
| `Sidekiq::Logger::Formatters::WithoutTimestamp` | タイムスタンプなしで出力 |
| `Sidekiq::Logger::Formatters::JSON`             | JSON形式で出力           |

ログ出力の例

`Sidekiq::Logger::Formatters::Pretty`

```
2019-08-31T15:36:07.569Z pid=82859 tid=11cy9br class=HardWorker jid=528f1b0ddc4a9d0690464fe4 INFO: start
```

`Sidekiq::Logger::Formatters::WithoutTimestamp`

```
pid=82859 tid=119pz7z class=HardWorker jid=b7f805c545c78770d30dc1fd elapsed=0.089 INFO: done
```

``Sidekiq::Logger::Formatters::JSON`

```KSP
{"ts":"2019-09-01T22:34:59.778Z","pid":90069,"tid":"104v8ph","lvl":"INFO","msg":"Running in ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-darwin17]"}
```

**参考**

* [Pluggable Log Formatter by Tensho · Pull Request #4093 · mperham/sidekiq](https://github.com/mperham/sidekiq/pull/4093)
* [Logging · mperham/sidekiq Wiki](https://github.com/mperham/sidekiq/wiki/Logging)

### ジョブ毎のログレベル指定が可能に

ワーカーの`.set`メソッドの引数で`log_level`を指定することで、特定のジョブのログの出力レベルを指定できるようになりました。

```ruby
MyWorker.set(log_level: :debug).perform_async(...)
```

SidekiqはデフォルトでRubyの標準ライブラリの[Logger](http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc/Logger.html)を使用しており、`log_level`で指定できるのは、`:unknown`、`:fatal`、`:warn`、`:info`、`:debug`になります。

**参考**

* [Support job-specific log levels by fatkodima · Pull Request #4287 · mperham/sidekiq](https://github.com/mperham/sidekiq/pull/4287)
* [Job-specific log levels · Issue #4286 · mperham/sidekiq](https://github.com/mperham/sidekiq/issues/4286)
* [Logging · mperham/sidekiq Wiki](https://github.com/mperham/sidekiq/wiki/Logging)

### ジョブのタグ付けが可能に

ワーカーのクラスの`sidekiq_options`に`tags`を指定することで、ジョブのタグ付けができるようになりました。

```ruby
class MyWorker
  include Sidekiq::Worker
  sidekiq_options tags: ['bank-ops', 'alpha']
  ...
end
```

**参考**

* [Support job tags by fatkodima · Pull Request #4280 · mperham/sidekiq](https://github.com/mperham/sidekiq/pull/4280)
* [Support for ad-hoc job tags? · Issue #4073 · mperham/sidekiq](https://github.com/mperham/sidekiq/issues/4073)

### Web UIでのダークモードのサポート

ダークモードがサポートされたブラウザ（IEやEdge、Operaは非サポート）でモードが有効な状態でWeb UI（Sidekiqの管理画面）にアクセスした場合は、黒ベースの画面で表示するようになりました。

**参考**

* [Initial pass at dark mode support, courtesy of @silent-e by mperham · Pull Request #4231 · mperham/sidekiq](https://github.com/mperham/sidekiq/pull/4231)

## 変更点

### `sidekiqctl`コマンドの削除

Sidekiqプロセスを停止、終了するために用いられてきた、`sidekiqctl`コマンドが廃止されました。Sidekiq 6では、このコマンドの代わりに`kill`コマンドでシグナルを送ることで、プロセスの停止、終了を行うことができます。

| 廃止されたコマンド | 代替となるコマンド | 説明     |
| ------------------ | ------------------ | ---- |
| `sidekiqctl quiet` | `kill -TSTP <pid>` | ワーカーの各スレッドを停止 |
| `sidekiqctl stop`  | `kill -TERM <pid>` | （上記コマンドの実行後に）プロセスを終了 |

### デーモン化を廃止し、サービスとして実行するように

サービスの管理をOS側（Systemd、Upstartなど）で行うようにするために、デーモン化を廃止しました。このため、本番環境などサーバーで動かす場合は、Sidekiqのサービスの設定ファイルを新たに作成し、サービスを有効にする必要があります。　

デーモン化の廃止に至った背景に関しては、Sidekiq作者による説明が以下のページにあります。

* [Don't Daemonize your Daemons!](https://www.mikeperham.com/2014/09/22/dont-daemonize-your-daemons/)

上記の記事では、以前から使用されてきた長時間動作するデーモンプロセスが、現在ではSystemdを使用したサービスに置き換えられていて、このことによって、ロギングやクラッシュ時の再実行などの処理を自前で実装することがなく、より堅牢な実行が可能になった、ということが書かれています。

Sidekiqのサービスの設定、起動や停止コマンドに関しては、この記事の次の節のアップグレードの部分を参考にしてください。

### `logfile`、`pidfile`コマンド引数の廃止　

上記のデーモン化の廃止に伴って、ログの出力や、プロセスIDの保持をSystemdなどのサービス側で行うようになったので、`sidekiq`コマンドから`logfile`、`pidfile`コマンド引数が削除されました。　

### デフォルトのシャットダウン時間が8秒から25秒に変更に

HerokuのDynoとAmazon ECS コンテナがアプリケーションのシャットダウンで30秒のタイムアウトを使用するようになったため、Sidekiqも8秒から25秒にタイムアウト時間を伸ばしました。以前の挙動に戻すには、オプション`-t 8`を`sidekiq`コマンドに対して指定します。

### `REDIS_PROVIDER`環境変数の検証を行うように

`REDIS_PROVIDER`環境変数は、RedisサーバーのURLを保持する**他の環境変数**を指定するのに使用します。もし、`REDIS_PROVIDER`変数に直接URLなどの不適切な文字列が指定されている場合は、以下のような警告を出すようになりました。

```
REDIS_PROVIDER should be set to the name of the variable which contains the Redis URL, not a URL itself.
Platforms like Heroku will sell addons that publish a *_URL variable.  You need to tell Sidekiq with REDIS_PROVIDER, e.g.:
```

**参考**

* [Validate REDIS_PROVIDER · Issue #3969 · mperham/sidekiq · GitHub](https://github.com/mperham/sidekiq/issues/3969)

### パフォーマンスの向上

生成するオブジェクトを減らすなど、細かいコードのチューニングを行うことによって、Sidekiq 5系列と比較して、10-15%実行速度が向上しているそうです。

**参考**

* [Optimize cloning of job payload by fatkodima · Pull Request #4303 · mperham/sidekiq](https://github.com/mperham/sidekiq/pull/4303)
* [Speedup UnitOfWork#queue_name by fatkodima · Pull Request #4299 · mperham/sidekiq](https://github.com/mperham/sidekiq/pull/4299)
* [Reduce allocated objects by fatkodima · Pull Request #4269 · mperham/sidekiq](https://github.com/mperham/sidekiq/pull/4269)



## アップグレード方法

### Gemのアップグレード

最初に、Gemfileに以下のように指定して、`bundle update`を実行して、Sidekiq 5系列の最新版にアップグレードします。

```ruby
gem 'sidekiq', '< 6'
```

Sidekiqを動かしてみて、非推奨の警告（Deprecation warnings）が出ないようにコードの修正を行います。

修正が完了したら、Gemfileに以下のように指定して、再度`bundle update`を実行して、Sidekiq 6.xへアップグレードします。

```ruby
gem 'sidekiq', '< 7'
```

### サービスの設定ファイルの作成

変更点で述べたように、本番環境では、Sidekiqプロセスはデーモンでなくサービスとして起動するようになったため、設定ファイルを作成する必要があります。

開発環境では、これまで通りユーザーが`sidekiq`コマンドを実行してフォアグラウンドで起動し、ログは標準出力および標準エラー出力に送信されるため、サービスとして起動させる必要はありません。

SystemdでSidekiqをサービスとして実行するには、以下のファイルを元に設定ファイルを生成し、サーバーの適切な場所(CentOSの場合は/usr/lib/systemd/system、Ubuntuの場合は/lib/systemd/system)に配置する必要があります。

https://raw.githubusercontent.com/mperham/sidekiq/master/examples/systemd/sidekiq.service

例えばrbenvのbundlerを使用していて、アプリケーションのディレクトリが`/var/www/myapp/current`の場合は、以下のように変更する必要があります。

```
WorkingDirectory=/var/www/myapp/current
...
ExecStart=/usr/local/rbenv/shims/bundle exec sidekiq -e production
```

ファイルの配置を行ったら、以下のコマンドでサービスを有効にします。

```
$ sudo systemctl enable sidekiq
```

### サービスの起動と停止

設定ファイルを配置後は、以下のコマンドでサービスの起動と停止を行います。

サーバーの起動

```
$ sudo systemctl start sidekiq
```

サーバーの停止

```
$ sudo systemctl stop sidekiq
```

## 参考URL

* [GitHub - mperham/sidekiq: Simple, efficient background processing for Ruby](https://github.com/mperham/sidekiq)
* [sidekiq/6.0-Upgrade.md at master · mperham/sidekiq · GitHub](https://github.com/mperham/sidekiq/blob/master/6.0-Upgrade.md)
* [Sidekiq Changes](https://github.com/mperham/sidekiq/blob/master/Changes.md)
* [Welcome to Sidekiq 6.0](https://www.mikeperham.com/2019/09/03/welcome-to-sidekiq-6.0/)