Delayed Job の導入とMackerelによる監視

本記事は[クービック Advent Calendar 2015](http://qiita.com/advent-calendar/2015/coubic)の24日目の記事です。

[クービック](https://coubic.com) と [Popcorn](https://popcorn.coubic.com/)では、ジョブキュー として Delayed Job を導入しています。本記事では、導入から監視までの流れをご紹介します。

なお、アプリケーションは Rails 4.2 で書かれています。


## 事前調査
Rails4.2 から [Active Job](http://railsguides.jp/active_job_basics.html) が導入されています。

> Active Jobの主要な目的は、Railsアプリを即席で作成した直後でも使用できる、自前のジョブ管理インフラを持つことです。これにより、Delayed JobとResqueなどのように、さまざまなジョブ実行機能のAPIの違いを気にせずにジョブフレームワーク機能やその他のgemを搭載することができるようになります。バックエンドでのキューイング作業では、操作方法以外のことを気にせずに済みます。さらに、ジョブ管理フレームワークを切り替える際にジョブを書き直さずに済みます。

様々なジョブキューバックエンドのアダプターを Active Job が引き受けてくれるので、これを利用しない手はないです。


[Active Job adapters](http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html) にサポートしているキューシステムのリストがあります。このリストに独自に調べた Middleware を足した表がこちらです。

|                   | Async | Queues | Delayed   | Priorities | Timeout | Retries | Middleware |
|-------------------|-------|--------|-----------|------------|---------|---------|---------|
| Backburner        | Yes   | Yes    | Yes       | Yes        | Job     | Global  |  beanstalkd |
| Delayed Job    | Yes   | Yes    | Yes       | Job        | Global  | Global  | RDBM, Redis, Mongoid, etc|
| Qu                | Yes   | Yes    | No        | No         | No      | Global  | Redis, Mongoid |
| Que               | Yes   | Yes    | Yes       | Job        | No      | Job     | PostgreSQL? |
| queue_classic     | Yes   | Yes    | No*       | No         | No      | No      | PostgreSQL |
| Resque            | Yes   | Yes    | Yes (Gem) | Queue      | Global  | Yes     | Redis |
| Sidekiq           | Yes   | Yes    | Yes       | Queue      | No      | Job     | Redis |
| Sneakers          | Yes   | Yes    | No        | Queue      | Queue   | No      | CLOUDAMQP |
| Sucker Punch    | Yes   | Yes    | No        | No         | No      | No      | Celluloid(threads) |
| Active Job Inline | No    | Yes    | N/A       | N/A        | N/A     | N/A     |  N/A |


Ruby Toolbox の Background Jobs は 以下のようになっておりました。 
![3a1148d26f299b376531bea5958c401c.png](https://qiita-image-store.s3.amazonaws.com/0/20304/f1a86a7c-255c-36e9-38ee-48624acfbb4e.png)


ジョブキューシステムの選定基準はいろいろあろうかと思いますが、新たな Middleware の管理が不要であること、ならびに 実績が多いことを重視し、Delayed Job を採用しました。

なお、[MySQL をキューに使用する 5 つの微妙な方法とその落とし穴 (翻訳版)](http://www.engineyard.co.jp/blog/2013/5-subtle-ways-youre-using-mysql-as-a-queue-and-why-itll-bite-you/) という記事で指摘されているように、サービス規模が大きくなった場合には見直しが必要かもしれません。


## 導入
delayed_job を Rails なアプリケーションに導入する記事は世にたくさんあるので、やったことを軽くご紹介するに留めておきます。

### Gemfile
```rb
gem 'delayed_job'
gem 'delayed_job_active_record'
gem 'daemons'
```

### config/application.rb
```rb
config.active_job.queue_adapter = :delayed_job
```

### config/initializers/delayed_job_config.rb
```rb
Delayed::Worker.default_queue_name = 'default'
Delayed::Worker.delay_jobs = !Rails.env.test?
Delayed::Worker.logger = Logger.new(File.join(Rails.root, 'log', 'delayed_job.log'))
```

delayed_jobs テーブルの migration を忘れずに。ここまででインストールは完了です。


次に、デーモン再起動を自動化する方法を探します。

[capistrano-delayed-job](https://github.com/capistrano-plugins/capistrano-delayed-job) と [capistrano3-delayed-job](https://github.com/platanus/capistrano3-delayed-job) があり、「どっちがいいの？」と疑問が湧きますが、Delayed Job の [wiki](https://github.com/collectiveidea/delayed_job/wiki/Delayed-Job-tasks-for-Capistrano-3) では後者が推奨されているので、素直に capistrano3-delayed-job を使います。

使い方は Capfile と deploy.rb に数行加えるだけ。カンタン。


Capfile

```rb
require 'capistrano/delayed-job'
```

config/deploy.rb

```rb
set :delayed_job_workers, 1
set :delayed_job_roles, [:app]
```

## 監視

ジョブキューシステムを導入したら、正常に稼動しているかを監視しなければなりません。

弊社では監視に Mackerel を利用していますが、Delayed Job についても [チェックプラグインの仕組み](http://help-ja.mackerel.io/entry/custom-checks)を利用して監視を行っています。

/etc/mackerel-agent/mackerel-agent.conf

```
[plugin.checks.delayed_job]
command = "sudo -uxxxx -i bash -c 'env RAILS_ENV=production bundle exec rails runner ./bin/mackerel_plugin_checks_delayed_job'"
```

bin/mackerel_plugin_checks_delayed_job

```rb
10.times do
  sleep 1

  # Find pid file
  pid_file = File.expand_path(File.join(File.dirname(__FILE__), '..', 'tmp', 'pids', 'delayed_job.pid'))

  # Read pid file
  File.open(pid_file, 'r') do |f|
    pid = f.read
  end
  next unless pid && pid =~ /\A\d+\n\z/

  # Find process
  ps = `ps #{pid}`
  next unless ps =~ /delayed_job/

  # Find failed jobs
  failed_jobs = Delayed::Job.where('last_error IS NOT NULL').where('attempts >= ?', 3)
  if failed_jobs.count > 0
    puts "failed jobs count: #{failed_jobs.count}"
    exit 2
  end

  puts "running"
  exit 0
end

puts "not running?"
exit 2
```

ほとんど説明不要な気もしますが、プロセスの存在監視と3回以上失敗しているジョブの監視をしています。

Go でプラグインを書くとカッコイイ気がするんですが、時間がなかったので（若干やっつけ仕事で）ruby スクリプトにしてます。


監視に引っ掛かった時は、Slack に Mackerel の bot が通知してくれます。

![delayed_job_mackerel.png](https://qiita-image-store.s3.amazonaws.com/0/20304/5080b00b-9690-6ede-d75d-18fd10c69e99.png)

## まとめ
[クービック](https://coubic.com) と [Popcorn](https://popcorn.coubic.com/)における Delayed Job の導入〜監視をご紹介しました。

何らかの参考になれば幸いです。