edge
詳細はrubyonrails.orgで: もっとRuby on Rails

Active Jobの基本

このガイドでは、バックグラウンドジョブの作成、エンキュー、実行を開始するために必要なすべての情報を提供します。

このガイドを読み終えると、以下のことがわかります。

1 Active Jobとは?

Active Jobは、ジョブを宣言し、さまざまなキューイングバックエンドで実行するためのフレームワークです。これらのジョブは、定期的なスケジュールされたクリーンアップ、請求料金、メーリングなど、さまざまなものです。実際には、小さな作業単位に分割して並列に実行できるものです。

2 Active Jobの目的

主な目的は、すべてのRailsアプリにジョブインフラストラクチャが備わっていることを保証することです。その上にフレームワークの機能や他のgemを構築できるようにすることで、Delayed JobやResqueなどのさまざまなジョブランナー間のAPIの違いを気にする必要がなくなります。キューイングバックエンドの選択は、運用上の問題になります。ジョブを書き直すことなくそれらの間を切り替えることができます。

注意:Railsにはデフォルトで非同期キューイングの実装があり、ジョブはインプロセスのスレッドプールで実行されます。ジョブは非同期に実行されますが、キューにあるジョブは再起動時に破棄されます。

3 ジョブの作成

このセクションでは、ジョブの作成とエンキューの手順についてのステップバイステップのガイドを提供します。

3.1 ジョブの作成

Active Jobは、ジョブを作成するためのRailsジェネレータを提供しています。以下のコマンドを実行すると、app/jobsにジョブが作成されます(test/jobsにはテストケースが作成されます)。

$ bin/rails generate job guests_cleanup
invoke  test_unit
create    test/jobs/guests_cleanup_job_test.rb
create  app/jobs/guests_cleanup_job.rb

特定のキューで実行されるジョブも作成できます。

$ bin/rails generate job guests_cleanup --queue urgent

ジェネレータを使用しない場合は、app/jobs内に独自のファイルを作成することもできますが、ApplicationJobから継承していることを確認してください。

ジョブの例は以下のようになります。

class GuestsCleanupJob < ApplicationJob
  queue_as :default

  def perform(*guests)
    # 何かしらの処理
  end
end

performメソッドには任意の数の引数を定義できることに注意してください。

もし既に抽象クラスがあり、その名前がApplicationJobと異なる場合は、--parentオプションを渡して別の抽象クラスを指定することができます。

$ bin/rails generate job process_payment --parent=payment_job
class ProcessPaymentJob < PaymentJob
  queue_as :default

  def perform(*args)
    # 何かしらの処理
  end
end

3.2 ジョブのエンキュー

perform_laterと、必要に応じてsetを使用してジョブをエンキューします。

# キューが空くとすぐに実行されるようにジョブをエンキューします。
GuestsCleanupJob.perform_later guest
# 明日の正午に実行されるようにジョブをエンキューします。
GuestsCleanupJob.set(wait_until: Date.tomorrow.noon).perform_later(guest)
# 1週間後に実行されるようにジョブをエンキューします。
GuestsCleanupJob.set(wait: 1.week).perform_later(guest)
# `perform_now`と`perform_later`は内部で`perform`を呼び出すため、
# 後者で定義されている引数の数だけ引数を渡すことができます。
GuestsCleanupJob.perform_later(guest1, guest2, filter: 'some_filter')

以上です!

4 ジョブの実行

ジョブをエンキューして実行するためには、プロダクション環境でキューイングバックエンドを設定する必要があります。つまり、Railsが使用するサードパーティのキューイングライブラリを決定する必要があります。Rails自体はインプロセスのキューイングシステムしか提供しておらず、ジョブはすべてRAMに保持されます。プロセスがクラッシュしたり、マシンがリセットされた場合、デフォルトの非同期バックエンドではすべてのジョブが失われます。これは、小規模なアプリや重要でないジョブには問題ありませんが、ほとんどのプロダクションアプリでは永続的なバックエンドを選択する必要があります。

4.1 バックエンド

Active Jobには、複数のキューイングバックエンド(Sidekiq、Resque、Delayed Jobなど)の組み込みアダプタがあります。アダプタの最新のリストについては、ActiveJob::QueueAdaptersのAPIドキュメントを参照してください。

4.2 バックエンドの設定

config.active_job.queue_adapterを使用して簡単にキューイングバックエンドを設定できます。

# config/application.rb
module YourApp
  class Application < Rails::Application
    # アダプタのgemがGemfileにあることを確認し、
    # アダプタの特定のインストールとデプロイ手順に従ってください。
    config.active_job.queue_adapter = :sidekiq
  end
end

ジョブごとにバックエンドを設定することもできます。

class GuestsCleanupJob < ApplicationJob
  self.queue_adapter = :resque
  # ...
end

# これでジョブは、`config.active_job.queue_adapter`で設定されたものを上書きして、
# `resque`をバックエンドキューアダプタとして使用します。

4.3 バックエンドの開始

ジョブはRailsアプリケーションと並行して実行されるため、ほとんどのキューイングライブラリは、ジョブ処理が機能するために、Railsアプリケーションの起動に加えて、ライブラリ固有のキューイングサービスを開始する必要があります。キューバックエンドの起動方法については、ライブラリのドキュメントを参照してください。

以下は、非包括的なドキュメントのリストです:

5 キュー

ほとんどのアダプタは複数のキューをサポートしています。Active Jobでは、queue_asを使用してジョブを特定のキューで実行するようにスケジュールできます。

class GuestsCleanupJob < ApplicationJob
  queue_as :low_priority
  # ...
end

すべてのジョブのキュー名にプレフィックスを付けるには、application.rbconfig.active_job.queue_name_prefixを使用します。

# config/application.rb
module YourApp
  class Application < Rails::Application
    config.active_job.queue_name_prefix = Rails.env
  end
end
# app/jobs/guests_cleanup_job.rb
class GuestsCleanupJob < ApplicationJob
  queue_as :low_priority
  # ...
end

# これで、ジョブは本番環境のproduction_low_priorityキューで実行され、ステージング環境のstaging_low_priorityキューで実行されます。

プレフィックスはジョブごとに個別に設定することもできます。

class GuestsCleanupJob < ApplicationJob
  queue_as :low_priority
  self.queue_name_prefix = nil
  # ...
end

# これで、ジョブのキュー名にはプレフィックスが付かず、`config.active_job.queue_name_prefix`で設定された内容を上書きします。

デフォルトのキュー名のプレフィックス区切り文字は「_」です。これは、application.rbconfig.active_job.queue_name_delimiterを設定することで変更できます。

# config/application.rb
module YourApp
  class Application < Rails::Application
    config.active_job.queue_name_prefix = Rails.env
    config.active_job.queue_name_delimiter = '.'
  end
end
# app/jobs/guests_cleanup_job.rb
class GuestsCleanupJob < ApplicationJob
  queue_as :low_priority
  # ...
end

# これで、ジョブは本番環境のproduction.low_priorityキューで実行され、ステージング環境のstaging.low_priorityキューで実行されます。

ジョブが実行されるキューをより細かく制御するには、set:queueオプションを渡すことができます。

MyJob.set(queue: :another_queue).perform_later(record)

ジョブレベルでキューを制御するには、queue_asにブロックを渡すこともできます。ブロックはジョブコンテキストで実行されるため(self.argumentsにアクセスできる)、キュー名を返す必要があります。

class ProcessVideoJob < ApplicationJob
  queue_as do
    video = self.arguments.first
    if video.owner.premium?
      :premium_videojobs
    else
      :videojobs
    end
  end

  def perform(video)
    # 動画の処理を行う
  end
end
ProcessVideoJob.perform_later(Video.last)

注意:キューイングバックエンドが指定したキュー名を「リッスン」できるようにしてください。一部のバックエンドでは、リッスンするキューを指定する必要があります。

6 コールバック

Active Jobは、ジョブのライフサイクル中にロジックをトリガーするためのフックを提供します。Railsの他のコールバックと同様に、コールバックを通常のメソッドとして実装し、マクロスタイルのクラスメソッドを使用してそれらをコールバックとして登録できます。

class GuestsCleanupJob < ApplicationJob
  queue_as :default

  around_perform :around_cleanup

  def perform
    # 後で何かを行う
  end

  private
    def around_cleanup
      # performの前に何かを行う
      yield
      # performの後に何かを行う
    end
end

マクロスタイルのクラスメソッドは、ブロックを受け取ることもできます。ブロック内のコードが1行に収まるほど短い場合は、このスタイルを使用することを検討してください。たとえば、すべてのジョブがエンキューされるたびにメトリクスを送信できます。

class ApplicationJob < ActiveJob::Base
  before_enqueue { |job| $statsd.increment "#{job.class.name.underscore}.enqueue" }
end

6.1 利用可能なコールバック

7 アクションメーラー

モダンなWebアプリケーションで最も一般的なジョブの1つは、リクエスト-レスポンスのサイクル外でメールを送信することです。これにより、ユーザーは待つ必要がありません。Active JobはAction Mailerと統合されているため、簡単に非同期にメールを送信できます。

# メールを即時に送信する場合は#deliver_nowを使用します
UserMailer.welcome(@user).deliver_now

# Active Jobを介してメールを送信する場合は#deliver_laterを使用します
UserMailer.welcome(@user).deliver_later

注意:非同期キューをRakeタスクから使用する場合(たとえば、.deliver_laterを使用してメールを送信する場合)、一般的にはうまく動作しません。Rakeが終了する前に、.deliver_laterのメールがすべて処理される前に、インプロセスのスレッドプールが削除される可能性があります。この問題を回避するためには、.deliver_nowを使用するか、開発環境で永続的なキューを実行してください。

8 国際化

各ジョブは、ジョブが作成されたときに設定されたI18n.localeを使用します。これは、メールを非同期に送信する場合に便利です。

I18n.locale = :eo

UserMailer.welcome(@user).deliver_later # メールはエスペラント語にローカライズされます。

9 引数のサポートされるタイプ

ActiveJobはデフォルトで以下のタイプの引数をサポートしています:

  • 基本的なタイプ(NilClassStringIntegerFloatBigDecimalTrueClassFalseClass
  • Symbol
  • Date
  • Time
  • DateTime
  • ActiveSupport::TimeWithZone
  • ActiveSupport::Duration
  • Hash(キーはStringまたはSymbolのタイプである必要があります)
  • ActiveSupport::HashWithIndifferentAccess
  • Array
  • Range
  • Module
  • Class

9.1 GlobalID

Active JobはパラメータにGlobalIDをサポートしています。これにより、クラス/IDのペアではなく、ライブなActive Recordオブジェクトをジョブに渡すことができます。これまでは、ジョブは次のようになっていました:

class TrashableCleanupJob < ApplicationJob
  def perform(trashable_class, trashable_id, depth)
    trashable = trashable_class.constantize.find(trashable_id)
    trashable.cleanup(depth)
  end
end

これからは、次のように簡単にできます:

class TrashableCleanupJob < ApplicationJob
  def perform(trashable, depth)
    trashable.cleanup(depth)
  end
end

これは、デフォルトでActive Recordクラスに混入されているGlobalID::Identificationをミックスインする任意のクラスで機能します。

9.2 シリアライザ

サポートされる引数のタイプを拡張することができます。独自のシリアライザを定義するだけです:

# app/serializers/money_serializer.rb
class MoneySerializer < ActiveJob::Serializers::ObjectSerializer
  # このシリアライザで引数をシリアライズする必要があるかどうかをチェックします。
  def serialize?(argument)
    argument.is_a? Money
  end

  # サポートされるオブジェクトタイプを使用して、オブジェクトをよりシンプルな表現に変換します。
  # 推奨される表現は、特定のキーを持つハッシュです。キーは基本的なタイプのみにする必要があります。
  # カスタムシリアライザタイプをハッシュに追加するために`super`を呼び出す必要があります。
  def serialize(money)
    super(
      "amount" => money.amount,
      "currency" => money.currency
    )
  end

  # シリアライズされた値を適切なオブジェクトに変換します。
  def deserialize(hash)
    Money.new(hash["amount"], hash["currency"])
  end
end

そして、このシリアライザをリストに追加します:

# config/initializers/custom_serializers.rb
Rails.application.config.active_job.custom_serializers << MoneySerializer

注意:初期化中にリロード可能なコードの自動読み込みはサポートされていません。したがって、シリアライザを1回だけロードされるように設定することをお勧めします。たとえば、次のようにconfig/application.rbを修正することができます:

# config/application.rb
module YourApp
  class Application < Rails::Application
    config.autoload_once_paths << Rails.root.join('app', 'serializers')
  end
end

10 例外

ジョブの実行中に発生した例外は、rescue_fromを使用して処理することができます:

class GuestsCleanupJob < ApplicationJob
  queue_as :default

  rescue_from(ActiveRecord::RecordNotFound) do |exception|
    # 例外を処理する
  end

  def perform
    # 何かを後で実行する
  end
end

ジョブからの例外が救出されない場合、ジョブは「失敗した」とみなされます。

10.1 失敗したジョブの再試行または破棄

失敗したジョブは、別の設定がない限り再試行されません。

retry_onまたはdiscard_onを使用して、失敗したジョブを再試行または破棄することができます。例:

class RemoteServiceJob < ApplicationJob
  retry_on CustomAppException # デフォルトでは3秒待機、5回の試行

  discard_on ActiveJob::DeserializationError

  def perform(*args)
    # CustomAppExceptionまたはActiveJob::DeserializationErrorが発生する可能性があります
  end
end

10.2 デシリアライズ

GlobalIDを使用すると、#performに渡された完全なActive Recordオブジェクトをシリアライズすることができます。

ジョブがエンキューされた後で#performメソッドが呼び出される前に、渡されたレコードが削除されると、Active JobはActiveJob::DeserializationError例外を発生させます。

11 ジョブのテスト

ジョブをテストする方法の詳細な手順については、テストガイドを参照してください。

12 デバッグ

ジョブの出所を特定するのに役立つ場合は、詳細なログ出力を有効にすることができます。

フィードバック

このガイドの品質向上にご協力ください。

タイポや事実の誤りを見つけた場合は、ぜひ貢献してください。 開始するには、ドキュメントへの貢献セクションを読んでください。

不完全なコンテンツや最新でない情報も見つかるかもしれません。 メインのドキュメントに不足しているドキュメントを追加してください。 修正済みかどうかは、まずEdge Guidesを確認してください。 スタイルと規約については、Ruby on Rails Guides Guidelinesを確認してください。

修正すべき点を見つけたが、自分で修正できない場合は、 問題を報告してください

そして最後に、Ruby on Railsのドキュメントに関するあらゆる議論は、公式のRuby on Railsフォーラムで大歓迎です。