目次

ActiveJobの概要

  • Job(ジョブ)とqueue(キュー)

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

  • ActiveJobを実装すべきケース

ActiveJobのバックエンド

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

簡単なjobを作成する

  • Jobの実装の準備
  • Jobをキューに入れる
  • バックエンドの設定

ActiveJobの概要

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

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

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

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

Job(ジョブ)とQueue(キュー)

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

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

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

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

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

about-job

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

about-queue

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

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

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

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

about-jobqueue

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


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

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

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

具体的には

  • メールの送信
  • 画像の処理
  • データを集計してCSVに落とす

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


ActiveJobと非同期バックエンド

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Sidekiqが提供しているAPI [翻訳]


ActiveJobの同期実行

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

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

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
$ 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

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

$ bin/rails generate job task

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

class TasksJob < ApplicationJob
  queue_as :default

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

ジョブの実行

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

def perform(title="掃除")
  Task.create!(title: title)
end

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

ジョブをキューに入れる

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

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

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

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

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

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

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同期的であり、キューには登録されず、呼び出された場合に直ぐに実行される

バックエンドの設定

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

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

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

https://gist.github.com/tomysmile/1b8a321e7c58499ef9f9441b2faa0aa8

gem 'sidekiq'
$ bundle install
module TaskJobSample
  class Application > Rails::Application
    config.active_job.queue_adapter = :sidekiq
  end
end

redisサーバを起動します。

$ redis-server

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

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

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

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

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

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

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

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

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

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

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