Posted at

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

More than 3 years have passed since last update.


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

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

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

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


Sidekiq Proという存在

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


  • Reliability: プロセスが死んだりRedisが死んでも、キューを復元できる

  • Batches: 複数のジョブをグループ化して進行管理できる

  • Support: サポート!!!

ここらへんは、Sidekiq Proを1年ほど使ってみて良かったところ、困ったところという@sumyappさんのスライドが非常に参考になります。


失敗したキューを知る

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

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

まず、Sidekiqを起動します。

$ bundle exec sidekiq -q default

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


jobs/hoge_job.rb

class HogeJob < ActiveJob::Base

queue_as :default

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


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

> 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件ありますね。

> rs = Sidekiq::RetrySet.new

> rs.count
=> 1

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

> 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があるので紹介します。

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

gem 'sidekiq'

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

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

sidekiq_failer.png

一覧だとこんな感じ。

FailedJobs1.png

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

FailedJobs2.png

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

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

Sidekiq::Failures.count

Sidekiq::Failures.reset_failures


ダッシュボード拡張Gem

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


sidekiq-history

sidekiq-history.png

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


sidekiq-statistic

sidekiq-statistic.png

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


失敗したらSlackに通知する

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

gem 'slack-incoming-webhooks'

Incoming WebHooksのAPIキーを発行するには、こちら


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

いい感じですね!

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


Zabbixで監視するためのAPI

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


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はプラグインがあるみたいです。


AirBrakeを使う

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

airbrake.png

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

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