Ruby on RailsのActiveJobとは?

# ActiveJobとは

## 目次
- ActiveJobの概要
  - Job（ジョブ）とqueue（キュー）

- どういった場面で使用するのか
  - ActiveJobを実装すべきケース

- ActiveJobのバックエンド
  - なぜ非同期バックエンドが必要なのか
  - よく採用される非同期バックエンド
  - ActiveJobと非同期バックエンドの直接利用

- ActiveJobの同期実行

- 簡単なjobを作成する
  - Jobの実装の準備
  - Jobをキューに入れる
  - バックエンドの設定

---

## ActiveJobの概要

今回はRubyonRailsの機能の一つである、「ActiveJob」について解説していきたいと思います。

ActiveJobについて、Railsガイドでは以下のように説明されています。

> Active Jobは、ジョブを宣言し、
> それによってバックエンドでさまざまな方法によるキュー操作を実行するためのフレームワークです。
> ジョブには、定期的なクリーンアップを始めとして、請求書発行やメール配信など、あらゆる処理がジョブになります。
> これらのジョブをより細かな作業単位に分割して並列実行することもできます。

このように記載されていますが、初学者の方だったり、経験の浅い方からすると、「ジョブ」や「キュー」といった単語に馴染みがなく、理解が難しいと思います。

### Job（ジョブ）とQueue（キュー）

ActiveJobについて考える前に、頻繁に出てくる`ジョブ`と`キュー`とは何かを理解するところから初めてみます。

|名称|読み方|概要|
|---|---|---|
|Job|ジョブ|コンピュータがする仕事の単位|
|Queue|キュー|データ構造の一つ。待ち行列ともいう|

ジョブは現実世界で例えるなら、

- 買い物にいく
- 料理をする
- 片付けをする

といったような、一つ一つのタスクのようなものです。

![about-job.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264684/737b1173-4053-14e7-2941-6199c108404a.png)


キューはタスクを登録するための`入れ物`とするとイメージしやすいかもしれません。

![about-queue.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264684/12aa7d24-a33a-6b37-f029-699bafed2a94.png)


キューの特徴は `先に登録されたものから先に実行する(先入れ先出し)`という点です。

|登録順|タスク内容|
|---|---|
|01|買い物にいく|
|02|料理をする|
|03|片付けをする|

このようなジョブを01から順番にキューに登録、実行した場合、`01買い物にいく`->`02料理をする`->`03片付けをする`の順で実行されます。

そして、登録したJobから逐次実行する。というフローを`ジョブキュー`と呼ばれたりもします。

![about-jobqueue.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264684/8e559934-6081-86fb-1ba6-9ea9a83f1f8d.png)


ActiveJobはこれらの処理をバックグラウンドで実行することができる機能のことをいいます。

---

## どういった場面で使用するのか

先述したようにActiveJobはバックグラウンドで実行ができるため、処理をアプリケーションの裏側で実行させることができます。

なので、ActiveJobは基本的に`リアルタイム性を伴わない`場合や`重たい処理`に使われることが多いです。

具体的には
- メールの送信
- 画像の処理
- データを集計してCSVに落とす

このような時間がかかる処理の場合には、先にレスポンスをクライアントへ返しておき、バックグラウンドで処理を別途実行します。

---

## ActiveJobと非同期バックエンド

### なぜ非同期バックエンドが必要なのか？

ActiveJobには非同期バックエンドなるサードパーティーのキューイングライブラリが必要です。

開発環境や、小規模アプリケーションの場合には、ActiveJobのみでも問題ないのですが、本番環境でアプリケーションを運用していくとなると、いろいろと不都合がでてきます。

Railsから提供されているActiveJobには、ジョブをメモリに保持するインプロセスのキューイングシステムだけなので、プロセスが切れたりするとジョブは全て失われます。

### よく採用される非同期バックエンド

そういった問題を解決するためによく採用されるのが Sidekiqやsucker_punchといったライブラリが挙げられます。

sucker_punchは非同期処理をスレッドで実行するため、Railsとプロセスを分ける必要がありません。万が一ジョブの実行中にRailsの再起動をすると、実行中のジョブは強制的に中断され、再起動後のリトライも自動では実行されません。

一方、Sidekiqは別プロセスを立てるため、Railsプロセスが終了したとしても、問題がなく、リトライも可能です。ただ、ジョブを永続化するために、RedisといったNoSQLも必要になります。

このほかにも`DeployedJob`や`Resque`などがありますが、実装要件に合ったものを選択します。

https://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html

### ActiveJobを使用せず、Sidekiqを利用する

ActiveJobを使用せず、直接Sidekiqを使用するということも可能です。

しかし、多くのプロジェクトでは、ActiveJob経由して、Sidekiqを利用します。そうすることで、非同期処理のライブラリがSidekiqから他のライブラリに変わったとしても、コードの流用ができます。

 ActiveJobを使用せず、Sidekiqを利用する方法は下記をご参照ください。

[Sidekiqが提供しているAPI [翻訳]](https://qiita.com/ts-3156/items/ec4608c7c9cf1494bcc1)

---

## ActiveJobの同期実行

ActiveJobは同期実行も可能です。

同期実行するケースは、ジョブの実行結果をテストする時が挙げられます。

```ruby:config/environments/test.rb
Rails.application.config.active_job.queue_adapter = :inline
```

このようにテストの環境設定ファイルに記述することで、テスト実行時の時のみ、ActiveJobを同期実行させることができます。

---

## 簡単なJobを作成する

ここからは、実際に簡単なjobを作成して、ActiveJobに対する理解を深めていきましょう。
今回は、Taskモデルを作成して、ActiveJobとSidekiqを使った処理を実行してみます。

### Job実装の準備

■ 開発環境
- macOS X Catalina
- Ruby 2.6.6
- RubyonRails 6.1.3
- sqlite3
- Redis 4.0

```shell
$ rails new active_job_sample
$ cd active_job_sample
$ bin/rails db:create
$ bin/rails generate model task title
$ bin/rails db:migrate
$ bin/rails server
```

`localhost:3000`にアクセスします。

![rails-welcome.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264684/26e7876d-2940-6bb1-0f7e-1ea0400a9af1.png)


ここまで確認できたら、次にジョブを作成します。
RailsにはJobを作成するコマンドが標準で用意されています。

```shell
$ bin/rails generate job task
```

これで、`ApplicationJob`を継承した`TasksJob`クラスが生成されました。

```ruby:app/jobs/tasks_job.rb
class TasksJob < ApplicationJob
  queue_as :default

  def perform(*args)
    # Do something later
  end
end
```

### ジョブの実行

先ほど作成したTasksJobクラスに以下記述を追記して、Jobを実行してみましょう。

```ruby:app/jobs/tasks_job.rb
def perform(title="掃除")
  Task.create!(title: title)
end
```

performメソッドは引数に`title`を受け取り、`Task.create!()`でDBにTaskを保存します。

### ジョブをキューに入れる

実際に、ジョブをキューに入れて実行してみましょう。

ここからはコンソールに入って操作します。

```shell
$ bin/rails console
irb(main) > TaskJob.perform_later
Enqueued TaskJob (Job ID: XXX)　=> # 省略
```

これでDBにデータが追加されたかと思うので、先ほど追加したデータ登録されているのか確認してみましょう。

```shell
irb(main) > Task.last
=> #<Task id: 1, title: "掃除", created_at: "XXX", updated_at: "XXX">
```

実行するタイミングも指定することが可能です。
以下の例では、`wait`引数を使用して、1分後に実行するジョブを追加します。

```shell
irb(main) > TaskJob.set(wait: 1.minute).perform_later
=> #<Task id: 2, title: "掃除", created_at: "XXX", updated_at: "XXX">
```

今回は`perform_later`というメソッドを使用して、キューにジョブを登録し、非同期実行しましたが、`perform_later`ともう一つ、`perform_now`といったメソッドも存在します。

perform_later,perform_nowどちらも、performを呼び出すことには変わりはないのですが、違いとしては以下の通りになります。

|メソッド名|概要|
|---|---|
|perform_later|`非同期的`であり、キューに登録され、実行する|
|perform_now|`同期的`であり、キューには登録されず、呼び出された場合に直ぐに実行される|

<!-- 参考： https://edgeapi.rubyonrails.org/classes/ActiveJob/Base.html  -->

### バックエンドの設定

次に、バックエンドとして、`sidekiq`を使った設定でジョブキューを実行してみましょう。

sidekiqを利用するためにはRedisが必要になりますので、ローカルでRedisサーバー起動できるようにしておいてください。

Redisの環境構築について、Mac OSXをご使用の方は下記のサイトをご参考ください。

[https://gist.github.com/tomysmile/1b8a321e7c58499ef9f9441b2faa0aa8](https://gist.github.com/tomysmile/1b8a321e7c58499ef9f9441b2faa0aa8)

```ruby:Gemfile
gem 'sidekiq'
```

```shell
$ bundle install
```

```ruby:config/application.rb
module TaskJobSample
  class Application > Rails::Application
    config.active_job.queue_adapter = :sidekiq
  end
end
```

redisサーバを起動します。

```shell
$ redis-server
```

これで、sidekiqのキューにジョブを登録する準備が整いました。

先ほどと同じようにコンソールからジョブをキューに追加してみます。

今回は引数を少し変えて追加します。

```shell
irb(main) > TaskJob.perform_later(title: "洗濯をする")
```

実行ができたら、一度結果を確認しましょう。

```shell
irb(main) > Task.last
=> #<Task id: 2, title: "掃除", created_at: "XXX", updated_at: "XXX">
```

まだsidekiqが起動していないので、データが登録されていないのがわかります。

それでは、実際にsidekiqを起動して、再び確認してみましょう。

```shell
$ bundle exec sidekiq
# 起動後別のシェルを起動する
irb(main) > Task.last
=> #<Task id: 3, title: "洗濯をする", created_at: "XXX", updated_at: "XXX">
```

新しくレコードが作成されているのが確認できました。

今回はActiveJobの基本的な実装のみの紹介になりましたが、ActiveJobでの例外処理の仕方、コールバック、テストの書き方などできることはまだまだ沢山あるので、また次の機会に紹介できたらと思います。


## 記事引用元
https://outsource-systems.com/aboutactivejob/