Sidekiqで非業の死を遂げたキューを知る方法

# 前置き：Rubyのキューイングシステム

Rails（Ruby）で非同期にキューイングしてくれるライブラリといえば[Sidekiq](http://sidekiq.org/)ですよねっていうくらいSidekiqが好きなんですが、世の中のシェア的にはResqueなのかな。でも、Sidekiqの方が安定的に動かせているので僕は好きです（delayed_jobは知らない）。

ShoryukenというAWS SQSを使ったキューイングシステムもあり、RedisじゃなくてSQS使いたいっていう場合は、これも良いのかもしれないですね。まぁ、ローカルの場合面倒そうな感じもありますけど。

- [phstc/shoryuken](https://github.com/phstc/shoryuken)

まぁともかくSidekiqを導入してキューイングするっていうのは、もう超絶簡単にできるわけです。できるんですけど、実際にラフに運用していくと、キューがいつの間にかこけて処理待ちで埋まってたり、レアなケースで例外投げて死んでたりするわけです。そんなSidekiqと仲良く暮らしていくために、死にゆくキューを知る方法をまとめてみました。

## Sidekiq Proという存在

Sidekiqにはエンタープライズ向けなPro版があります。年間$950。それなりにしますね。このProと通常版、どう違うのかというと…

- Reliability: プロセスが死んだりRedisが死んでも、キューを復元できる
- Batches: 複数のジョブをグループ化して進行管理できる
- Support: サポート！！！

ここらへんは、[Sidekiq Proを1年ほど使ってみて良かったところ、困ったところ](http://www.slideshare.net/SumiKoichiro/sidekiq-pro1-rb-29th-shinjukurb)という[@sumyapp](https://twitter.com/sumyapp)さんのスライドが非常に参考になります。

# 失敗したキューを知る

普通にコードがミスってたり、データがおかしかったりして例外投げて死んでいったキューを知りたいというケースであれば、Pro版を使わなくともなんとかなります。

以降のサンプルスクリプトはRailsのActiveJob経由での話になります。が、まぁ適当に置き換えてもらえれば素のSidekiqでも問題ないと思います。

まず、Sidekiqを起動します。

```
$ bundle exec sidekiq -q default
```

適当なジョブファイルを作成します。そして、実行したら必ず例外を投げて死亡するようにしておきます。

```ruby:jobs/hoge_job.rb
class HogeJob < ActiveJob::Base
  queue_as :default

  def perform(hoge_id:)
    raise "error!" # 明示的に例外投げる
  end
end
```

rails consoleからキューを積みます。すると、Sidekiqを起動しているコンソール上では、当然のようにエラーログが出ます。

```rails_console
> HogeJob.perform_later(hoge_id: 100)

=> Enqueued HogeJob (Job ID: e0693a53-9860-4e73-9479-674dd84ccaac) to Sidekiq(default) with arguments: {:hoge_id=>100}
```

次に、rails consoleでSidekiq::RetrySetを取得します。ここにはリトライ内容が入っています。countで件数を見ると1件ありますね。

```rails_console
> rs = Sidekiq::RetrySet.new
> rs.count
=> 1
```

次は中身を見てみましょう。

```rails_console
> rs.first
=> #<Sidekiq::SortedEntry:0x007ff571fece00
 @item=
  {"class"=>"ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper",
   "queue"=>"default",
   "args"=>
    [{"job_class"=>"HogeJob",
      "job_id"=>"e0693a53-9860-4e73-9479-674dd84ccaac",
      "queue_name"=>"default",
      "arguments"=>[{"hoge_id"=>100, "_aj_symbol_keys"=>["hoge_id"]}]}],
   "retry"=>true,
   "jid"=>"1044bc2eb37b250b8eea6137",
   "created_at"=>1450159336.0921764,
   "enqueued_at"=>1450159336.0922325,
   "error_message"=>"error!",
   "error_class"=>"RuntimeError",
   "failed_at"=>1450159336.0988874,
   "retry_count"=>0},
 @parent=#<Sidekiq::RetrySet:0x007ff57208ef48 @_size=2, @name="retry">,
 @queue="default",
 @score=1450159361.0989082,
 @value=
  "{\"class\":\"ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper\",\"queue\":\"default\",\"args\":[{\"job_class\":\"HogeJob\",\"job_id\":\"e0693a53-9860-4e73-9479-674dd84ccaac\",\"queue_name\":\"default\",\"arguments\":[{\"hoge_id\":100,\"_aj_symbol_keys\":[\"hoge_id\"]}]}],\"retry\":true,\"jid\":\"1044bc2eb37b250b8eea6137\",\"created_at\":1450159336.0921764,\"enqueued_at\":1450159336.0922325,\"error_message\":\"error!\",\"error_class\":\"RuntimeError\",\"failed_at\":1450159336.0988874,\"retry_count\":0}">
```

素晴らしい。どんな引数で、どんなエラーが発生したかが分かりますね。

## Sidekiqダッシュボードに表示する

手動でSidekiq::RetrySetを見れば失敗したキューの情報を取得できます。が、このままでは使いにくいです。そこで、Sidekiqのダッシュボードに表示するようにします。ここで良い感じにやってくれるGemがあるので紹介します。

- [mhfs/sidekiq-failures](https://github.com/mhfs/sidekiq-failures/)

基本、こいつを入れればOKです。

```Gemfile
gem 'sidekiq'
gem 'sinatra', require: false
gem 'sidekiq-failures'
```

rails serverを立ちあげて http://exampl.ecom/sidekiq にアクセスすると、「失敗」というメニューが追加されたダッシュボードが表示されます。

![sidekiq_failer.png](https://qiita-image-store.s3.amazonaws.com/0/2152/a6a1e15f-7222-9c22-0e15-5273aaf4a8f8.png "sidekiq_failer.png")

一覧だとこんな感じ。

![FailedJobs1.png](https://qiita-image-store.s3.amazonaws.com/0/2152/91ba44c9-40b6-7f3e-4f00-441cdbc63b69.png "FailedJobs1.png")

クリックして詳細も見ることが出来ます。

![FailedJobs2.png](https://qiita-image-store.s3.amazonaws.com/0/2152/03b19fc4-6f47-0660-c88a-274963b8559f.png "FailedJobs2.png")

今すぐ再試行などもできるので、使い勝手もいいですね。

API的にも使えそうなヘルパーメソッドも用意されています。

```ruby
Sidekiq::Failures.count

Sidekiq::Failures.reset_failures
```

## ダッシュボード拡張Gem

mhfs/sidekiq-failures以外にもダッシュボードを拡張してキューの一覧が見られるGemを後輩エンジニアから教えてもらいました。どちらもGemfileに突っ込んであげるだけで使えるので簡単。

- [russ/sidekiq-history](https://github.com/russ/sidekiq-history)
- [davydovanton/sidekiq-statistic](https://github.com/davydovanton/sidekiq-statistic)

### sidekiq-history

![sidekiq-history.png](https://qiita-image-store.s3.amazonaws.com/0/2152/e65c9bc2-e95d-44db-7039-9283cb01b532.png)

こんな感じで表示されます。失敗成功関係なしに、全キューの情報が見られるようです。これはこれで便利…！

### sidekiq-statistic

![sidekiq-statistic.png](https://qiita-image-store.s3.amazonaws.com/0/2152/e30e0ca4-c207-0b86-73fd-9603a4012512.png)

sidekiq-statisticは高機能で、通常のキューと失敗したキューを見ることができます。また、各キューにかかった処理時間の平均値とか出してくれます。ただ、失敗したキューの詳細を見ることはできないようです。できるのかな？

### 失敗したらSlackに通知する

今やSlackに通知を送るのはデファクトスタンダード的なノリになっているのでSlackのWebHookを使ってキューが失敗したら通知してあげるようにしましょう。素晴らしいGemがあるので、それを利用します。

```Gemfile
gem 'slack-incoming-webhooks'
```

Incoming WebHooksのAPIキーを発行するには、[こちら](https://basicinc.slack.com/services/new/incoming-webhook)。

```ruby:jobs/hoge_job.rb
class HogeJob < ActiveJob::Base
  queue_as :default

  rescue_from Exception do |e|
    slack = Slack::Incoming::Webhooks.new 'https://hooks.slack.com/services/poyo/fuga/hogepiyo'

    attachments = [{
      title: "Sidekiq failure",
      text: "%s failure \n QueueName %s \n Error %s \n JobId %s \n Arguments %s" % [self.class, self.queue_name, e.inspect, self.job_id, self.arguments],
      color: "#fb2489"
    }]
    slack.post "", attachments: attachments
  end
  
  def perform(hoge_id:)
    raise "error!"
  end
end
```

これで、予め設定しておいたSlackチャンネル宛にキューの情報が投げられます。

![slack.png](https://qiita-image-store.s3.amazonaws.com/0/2152/2460c80f-4d83-936d-8da3-c3795332ade2.png "slack.png")

いい感じですね！

あとは、再実行とかをSlackBotに指示できたりすればChatOps的ですね。

## Zabbixで監視するためのAPI

ZabbixにSidekiqの状態を教えてあげるAPIサーバを設定します。

```ruby:config/routes.rb
require 'sidekiq/api'

# 積んでいるキューの数
get "queue-status" => proc { [200, {"Content-Type" => "text/plain"}, [Sidekiq::Queue.new.size.to_s ]] }

# リトライ対象のキューの数
get "queue-retry" => proc { [200, {"Content-Type" => "text/plain"}, [Sidekiq::RetrySet.new.size.to_s ]] }

# キュー待ち時間
get "queue-latency" => proc { [200, {"Content-Type" => "text/plain"}, [Sidekiq::Queue.new.latency.to_s]] }
```

これで、 http://example.com/queue-retry でリトライ対象のキューの数が取得できます。あとはZabbixの設定をすればOK。nagiosはプラグインがあるみたいです。

- [wanelo/nagios-checks](https://github.com/wanelo/nagios-checks)

## AirBrakeを使う

AirBrakeという素晴らしいサービスを使っていればエラーを検知することができるので、こっちもオススメです。無料でも使えますが、有料プランにするか、自分でサーバをたててErrbitを動かすかが良いですね。お金があるなら、有料プランのほうが楽ちんです。

![airbrake.png](https://qiita-image-store.s3.amazonaws.com/0/2152/9703b732-15df-9067-c7f1-d157f64ac695.png "airbrake.png")

AirBrakeでも失敗内容が詳しく見られるので、対応が可能です。

以上、こんな感じでSidekiqで静かにそして非業の死を遂げたキューを知る方法をまとめてみました。それぞれ、運用にあったやり方があると思うので、より良い方法あるよーというのがあれば是非教えて下さい。けっこうキューイングシステムを運用していくとぶち当たる壁って多いので…。