前置き: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
適当なジョブファイルを作成します。そして、実行したら必ず例外を投げて死亡するようにしておきます。
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 にアクセスすると、「失敗」というメニューが追加されたダッシュボードが表示されます。
一覧だとこんな感じ。
クリックして詳細も見ることが出来ます。
今すぐ再試行などもできるので、使い勝手もいいですね。
API的にも使えそうなヘルパーメソッドも用意されています。
Sidekiq::Failures.count
Sidekiq::Failures.reset_failures
ダッシュボード拡張Gem
mhfs/sidekiq-failures以外にもダッシュボードを拡張してキューの一覧が見られるGemを後輩エンジニアから教えてもらいました。どちらもGemfileに突っ込んであげるだけで使えるので簡単。
sidekiq-history
こんな感じで表示されます。失敗成功関係なしに、全キューの情報が見られるようです。これはこれで便利…!
sidekiq-statistic
sidekiq-statisticは高機能で、通常のキューと失敗したキューを見ることができます。また、各キューにかかった処理時間の平均値とか出してくれます。ただ、失敗したキューの詳細を見ることはできないようです。できるのかな?
失敗したらSlackに通知する
今やSlackに通知を送るのはデファクトスタンダード的なノリになっているのでSlackのWebHookを使ってキューが失敗したら通知してあげるようにしましょう。素晴らしいGemがあるので、それを利用します。
gem 'slack-incoming-webhooks'
Incoming WebHooksのAPIキーを発行するには、こちら。
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チャンネル宛にキューの情報が投げられます。
いい感じですね!
あとは、再実行とかをSlackBotに指示できたりすればChatOps的ですね。
Zabbixで監視するためのAPI
ZabbixにSidekiqの状態を教えてあげるAPIサーバを設定します。
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でも失敗内容が詳しく見られるので、対応が可能です。
以上、こんな感じでSidekiqで静かにそして非業の死を遂げたキューを知る方法をまとめてみました。それぞれ、運用にあったやり方があると思うので、より良い方法あるよーというのがあれば是非教えて下さい。けっこうキューイングシステムを運用していくとぶち当たる壁って多いので…。