[Rails]ActiveJobについて、、！

# ActiveJobとは、、？
ジョブを宣言し、それによってバックエンドでさまざまな方法によるキュー操作を実行するためのフレームワーク。Active Jobを利用することによって、メールの送信や、バッチ処理などをバックグラウンドで実行することが可能になります。

##ActiveJobの予備知識
###キュー
first in, first outのデータ構造。スタックの逆。

###ジョブ
定期的なクリーンアップを始めとして、請求書発行やメール配信など、あらゆる処理がジョブになる。これらのジョブをより細かな作業単位に分割して並列実行することもできる。

###Active Jobの目的
- Delayed JobとResqueなどのように、さまざまなジョブ実行機能のAPIの違いを気にせずにジョブフレームワーク機能やその他のgemを搭載することができるようになる。
- バックエンドでのキューイング作業では、操作方法以外のことを気にせずに済む。
- ジョブ管理フレームワークを切り替える際にジョブを書き直さずに済む。

###Resque
- RMagickなどのメモリリークが存在するコードでも不安なくデプロイできる。
- エコシステムが出来上がってる。
- 毎回forkするので長時間ジョブ向き。
- キューのストレージとしてRedisが必要。
- メンテが追いついてない。

###Delayed Job
- DMに専用テーブル作成が必要。
- メモリリークしてるコードがあれば定期的な再起動が必要。

###Sidekiq
- Resque互換API。
- 並列に動作するので、外部サイトへのAPI呼び出しなど、I/O待ちの比率が大きいような用途で使うのに便利。
- 1個のプロセスで動作させられるのでメモリ使用量が少なく経済的。
- プロセス肥大化には弱い。
- コネクションプールの扱い。
- 並列以外の扱い。

# Jobの実装
渡されたメッセージをDBレコードに保存する非同期ジョブ機能を実装する。

1.ジョブクラスを生成する。

```bash
rails g job async_log
```

2.生成したジョブクラスを実装する。

```ruby
class AsyncLogJob < ApplicationJob
  # 保存するキューを指定
  queue_as :default

  # 実行したい処理を記載する
  def perform(message: "hello")
    AsyncLog.create!(message: message)
  end
end
```

3.ジョブをキューに入れる。(コンソールで実行する)

```ruby
irb(main):001:0> AsyncLogJob.perform_later
#=> バックエンドキューにジョブを追加し、非同期実行する

# ジョブが実行されたか確認する
irb(main):002:0> AsyncLog.first
#=> #<AsyncLog id: 1, message: "hello", created_at: "2021-07-11 06:26:24.405744000 +0000", updated_at: "2021-07-11 06:26:24.405744000 +0000">
```

## ジョブ実装時のTips

### 実行するタイミングを指定する

```ruby
# 5分後に処理が実行される
AsyncLogJob.set(wait: 5.minute).perform_later

# 翌日に処理が実行される
AsyncLogJob.set(wait_until: Date.tomorrow).perform_later
```

### 複数キューの管理をする
1.追加するキューを指定する。

```ruby
class AsyncLogJob < ApplicationJob
  # 追加するキューを指定する
  queue_as :async_log # デフォルトではdefaultが指定されている
  ...
end
```

2.キューの一覧を作成する。

```config/sidekiq.yml
:queues:
  - default # デフォルト使用するキュー
  - async_log # 追加したキュー
  - mailers # Action Mailerで利用するキュー
```

## ジョブへ渡す引数の制限

- NilClass
- String
- Integer
- Float
- BigDecimal
- TrueClass
- FalseClass
- Symbol
- 日時を扱う型(ActiveSupport::TimeWithZone, Time, Date, ActiveSupport::Durationなど)
- Hash(キーはStringかSymbol)
- Array
- Active Recordオブジェクト

## バックエンドの設定
デフォルト設定では、プロセスの再起動時にジョブが失われるなど、本番環境向きではないため、`Sidekiq`を使う。

1.Gemfile二以下を追記して、`bundle install`を実行する。

```ruby
gem 'sidekiq'
```

2.アダプターを設定する。
※環境別で変更したい場合は、`config/environments/***.rb`に記載する。

```config/application.rb
module WebpackerSample
  class Application < Rails::Application
    ...
    config.active_job.queue_adapter = :sidekiq
    ...
  end
end
```

## ジョブの例外処理
### ジョブのリトライ

```app/jobs/async_log_job.rb
class AsyncLogJob < ApplicationJob
  ...
  # 補足する例外を指定し、必要であればオプションを指定する
  retry_on StandardError, wait: 5.seconds, attempts: 3
end
```

#### 利用できるオプション

| オプション名 | 概要 | デフォルト値 |
|:-:|:-:|:-:|
| wait  | リトライまで指定時間待つ  | 3.seconds  |
| attempts  | リトライ回数  | 5  |
| queue  | リトライ時に追加するキュー  | nil  |
| priority  | 優先度  | nil  |

### ジョブの破棄

```app/jobs/async_log_job.rb
class AsyncLogJob < ApplicationJob
  ...
  # 指定したエラーが発生した場合にジョブを破棄する
  discard_on StandardError
end
```

3.Redis環境をセットアップする。

```bash
$ docker pull redis

$ docker run -p 6379:6379 redis
```

4.Sidekiqを起動する。

```bash
$ bundle exec sidekiq
```

5.キューの状態を確認するUIを開発環境限定で閲覧可能とする。

```config/routes.rb
Rails.application.routes.draw do
  ...
  if Rails.env.development?
    require 'sidekiq/web'
    mount Sidekiq::web => '/sidekiq'
  end
  ...
end
```

## 利用判断基準
ActiveJobを利用してSidekiq(等の非同期バックエンド)を使うか、Sidekiq(等の非同期バックエンド)のAPIを直接使うかの判断に迷った場合は下記を参照する。

### ActiveJobを使うと良いケース
- 標準的な機能のみ使用する
- プロジェクト初期段階でバックエンド選択をまだしていない
- バックエンドを差し替える可能性がある
- ActiveJobに依存している機能(Action Mailerなど)を使う
- ActiveRecordオブジェクトをキューへ追加するときに、デフォルトで用意されているGlobal IDを使った変換処理を利用する

### 非同期バックエンドを使うと良いケース
- ActiveJobから使えない機能がある場合
    - Sidekiqでは外部gem、Sidekiq Proなどが提供している機能)

#参考
[Rails 4.2で導入されたActive Jobを使ってみよう](https://qiita.com/ryohashimoto/items/2f8fd685920a5318def4)

[【Rails, Linux】『Active Job』と『ジョブ』についてまとめてみた](https://qiita.com/yokoto/items/e7d0e5a7f9ad152293a1)

[Active Jobの基礎]
(https://railsguides.jp/active_job_basics.html)