Skip to content

active_job

Active Job Active Jobは、Railsアプリケーションでジョブ(時間のかかる処理)をキューに入れて、非同期的に実行するためのフレームワークです。これにより、ユーザーのリクエストに対するレスポンスを素早く返し、ユーザーエクスペリエンスを向上させることができます。

なぜActive Jobを使うのか? ウェブアプリケーションには、ユーザーの応答を待たずにバックグラウンドで処理すべきタスクが数多くあります。例えば、以下のような処理です。

メール送信: ユーザー登録後のウェルカムメール送信など。

画像・動画の処理: アップロードされた画像のサムネイル生成など。

外部APIへのリクエスト: 外部サービスとのデータ同期など。

これらの処理をリクエストと同時に実行すると、ユーザーは処理が完了するまで待つことになり、ページの読み込みが遅くなります。Active Jobは、これらのタスクを「ジョブ」として扱い、後で実行することで、ウェブサーバーの負荷を軽減し、ユーザーへのレスポンスタイムを短縮します。

ジョブの作成と実行 ジョブの生成: 以下のコマンドでジョブを作成します。

Bash

rails generate job GuestSignup このコマンドはapp/jobs/guest_signup_job.rbというファイルを生成します。

ジョブの定義: 生成されたファイルに、実行したい処理をperformメソッド内に記述します。

app/jobs/guest_signup_job.rb
class GuestSignupJob < ApplicationJob
queue_as :default
def perform(guest_id)
# まさかり:引数にはIDを渡し、perform内で再取得する
guest = Guest.find(guest_id)
# ゲストユーザーにウェルカムメールを送信
GuestMailer.welcome_email(guest).deliver_now
end
end

⚠️ 引数に「オブジェクト」を渡してはいけない

Section titled “⚠️ 引数に「オブジェクト」を渡してはいけない”

提供されたコードにあるGuestSignupJob.perform_later(@guest)は、実はRails初心者が見事にハマる罠です。

重要な理解: ジョブの引数にActive Recordのインスタンスを直接渡してはいけません。

問題点: ジョブがキューに入ってから実行されるまでの間に、データベースの@guestが削除されたらどうなるでしょうか?ジョブ実行時にActiveRecord::RecordNotFoundでクラッシュします。

# ❌ 危険: Active Recordのインスタンスを直接渡す
def create
@guest = Guest.new(guest_params)
if @guest.save
GuestSignupJob.perform_later(@guest) # 問題: オブジェクトを直接渡している
redirect_to @guest, notice: 'ユーザー登録が完了しました。'
else
render :new
end
end
# 問題: ジョブ実行前に@guestが削除された場合、ActiveRecord::RecordNotFoundが発生

改善案: 引数にはguest.id(整数や文字列)を渡し、performメソッドの中でGuest.find(id)し直すのが鉄則です。

# ✅ 安全: IDを渡す
def create
@guest = Guest.new(guest_params)
if @guest.save
GuestSignupJob.perform_later(@guest.id) # IDを渡す
redirect_to @guest, notice: 'ユーザー登録が完了しました。'
else
render :new
end
end
# app/jobs/guest_signup_job.rb
class GuestSignupJob < ApplicationJob
def perform(guest_id)
guest = Guest.find(guest_id) # perform内で再取得
GuestMailer.welcome_email(guest).deliver_now
end
end

注意: GlobalIDという仕組みでRailsがよしなにやってくれる場合もありますが、基本は「IDを渡す」と覚える方が安全です。

ジョブのキューへの追加: コントローラーやモデルからperform_laterメソッドを呼び出して、ジョブをキューに追加します。

app/controllers/guests_controller.rb
def create
@guest = Guest.new(guest_params)
if @guest.save
# ジョブをキューに追加(IDを渡す)
GuestSignupJob.perform_later(@guest.id)
redirect_to @guest, notice: 'ユーザー登録が完了しました。'
else
render :new
end
end

⚠️ 「冪等性(べきとうせい)」の確保

Section titled “⚠️ 「冪等性(べきとうせい)」の確保”

これがバックグラウンド処理において最も重要な概念です。

重要な理解: 「ジョブは2回実行される可能性がある」という前提でコードを書かなければなりません。

問題点: Sidekiqは処理に失敗すると自動でリトライします。「メール送信」ジョブが、送信完了直後にネットワークエラーで「失敗」と判定された場合、リトライによって同じメールが2通届きます。

# ❌ 危険: 冪等性がないジョブ
class WelcomeEmailJob < ApplicationJob
def perform(user_id)
user = User.find(user_id)
UserMailer.welcome_email(user).deliver_now
# 問題: リトライされると、同じメールが複数回送信される
end
end

改善案: 「既に送信済みフラグが立っていたら何もしない」といった、何度実行しても結果が変わらない(冪等な)設計を徹底しましょう。

# ✅ 安全: 冪等性のあるジョブ
class WelcomeEmailJob < ApplicationJob
def perform(user_id)
user = User.find(user_id)
# 既に送信済みなら何もしない(冪等性の確保)
return if user.welcome_email_sent?
UserMailer.welcome_email(user).deliver_now
user.update(welcome_email_sent_at: Time.current)
end
end

この設計により、リトライされても同じメールが複数回送信されることを防げます。 ジョブの実行環境とSidekiq Active Jobは、ジョブを実際に実行するためのバックエンド(ジョブアダプター)を切り替えることができます。

asyncアダプター: デフォルトのアダプターで、開発環境で手軽に非同期処理を試すことができます。ただし、プロセスが終了するとキューがクリアされるため、本番環境での利用は推奨されません。

Sidekiq: 本番環境で最もよく使われるアダプターの一つです。Redisをバックエンドに使い、マルチスレッドで動作するため、高いスループットと低メモリ消費が特徴です。

⚠️ Active Job vs Sidekiq (Worker) の使い分け

Section titled “⚠️ Active Job vs Sidekiq (Worker) の使い分け”

Rails 4.2以降はActive Jobが標準ですが、Sidekiqには独自のSidekiq::Worker(現在はSidekiq::Job)という書き方もあります。

重要な理解: 「とりあえずActive Job」は正解ですが、Sidekiq特有の高度な機能(バッチ処理、詳細なリトライ制御、レートリミット)をフル活用したい場合は、Active Jobを介さず直接SidekiqのAPIを使う方が有利なケースがあります。

問題点: Active Jobは「共通規格」であるため、Sidekiq独自の便利機能が一部削ぎ落とされています。

# Active Job経由(共通規格)
class PaymentJob < ApplicationJob
queue_as :default
def perform(order_id)
# 制限: Sidekiqの高度な機能が使えない
end
end
# Sidekiqネイティブ(直接API)
class PaymentJob
include Sidekiq::Job
sidekiq_options queue: 'default', retry: 5, backtrace: true
def perform(order_id)
# 利点: Sidekiqの全機能が使える
# - バッチ処理
# - 詳細なリトライ制御
# - レートリミット
end
end

改善案: 基本はActive Jobで書き、パフォーマンスや複雑な制御が必要になったらSidekiqネイティブな書き方を検討するという使い分けを意識しましょう。

ケース使用する方法理由
基本的な非同期処理Active Job共通規格で移植性が高い
バッチ処理が必要Sidekiq::JobSidekiqのバッチ機能が必要
詳細なリトライ制御Sidekiq::Jobリトライ戦略の細かい制御が必要
レートリミットが必要Sidekiq::JobSidekiqのレートリミット機能が必要

Sidekiqの導入手順 Gemの追加: Gemfileに以下の行を追加し、bundle installを実行します。

Ruby

gem ‘sidekiq’ アダプターの設定: config/application.rbに、Active JobのアダプターとしてSidekiqを設定します。

Ruby

config.active_job.queue_adapter = :sidekiq Sidekiqの起動: Redisをインストールし、ターミナルで以下のコマンドを実行してSidekiqプロセスを起動します。

Bash

bundle exec sidekiq このように、Active JobとSidekiqを組み合わせることで、アプリケーションの要件に合わせて、スケーラブルで堅牢な非同期処理を実装することが可能になります。