active_job
Active Job Active Jobは、Railsアプリケーションでジョブ(時間のかかる処理)をキューに入れて、非同期的に実行するためのフレームワークです。これにより、ユーザーのリクエストに対するレスポンスを素早く返し、ユーザーエクスペリエンスを向上させることができます。
なぜActive Jobを使うのか? ウェブアプリケーションには、ユーザーの応答を待たずにバックグラウンドで処理すべきタスクが数多くあります。例えば、以下のような処理です。
メール送信: ユーザー登録後のウェルカムメール送信など。
画像・動画の処理: アップロードされた画像のサムネイル生成など。
外部APIへのリクエスト: 外部サービスとのデータ同期など。
これらの処理をリクエストと同時に実行すると、ユーザーは処理が完了するまで待つことになり、ページの読み込みが遅くなります。Active Jobは、これらのタスクを「ジョブ」として扱い、後で実行することで、ウェブサーバーの負荷を軽減し、ユーザーへのレスポンスタイムを短縮します。
ジョブの作成と実行 ジョブの生成: 以下のコマンドでジョブを作成します。
Bash
rails generate job GuestSignup このコマンドはapp/jobs/guest_signup_job.rbというファイルを生成します。
ジョブの定義: 生成されたファイルに、実行したい処理をperformメソッド内に記述します。
class GuestSignupJob < ApplicationJob queue_as :default
def perform(guest_id) # まさかり:引数にはIDを渡し、perform内で再取得する guest = Guest.find(guest_id) # ゲストユーザーにウェルカムメールを送信 GuestMailer.welcome_email(guest).deliver_now endend⚠️ 引数に「オブジェクト」を渡してはいけない
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 endend
# 問題: ジョブ実行前に@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 endend
# app/jobs/guest_signup_job.rbclass GuestSignupJob < ApplicationJob def perform(guest_id) guest = Guest.find(guest_id) # perform内で再取得 GuestMailer.welcome_email(guest).deliver_now endend注意: GlobalIDという仕組みでRailsがよしなにやってくれる場合もありますが、基本は「IDを渡す」と覚える方が安全です。
ジョブのキューへの追加:
コントローラーやモデルからperform_laterメソッドを呼び出して、ジョブをキューに追加します。
def create @guest = Guest.new(guest_params) if @guest.save # ジョブをキューに追加(IDを渡す) GuestSignupJob.perform_later(@guest.id) redirect_to @guest, notice: 'ユーザー登録が完了しました。' else render :new endend⚠️ 「冪等性(べきとうせい)」の確保
Section titled “⚠️ 「冪等性(べきとうせい)」の確保”これがバックグラウンド処理において最も重要な概念です。
重要な理解: 「ジョブは2回実行される可能性がある」という前提でコードを書かなければなりません。
問題点: Sidekiqは処理に失敗すると自動でリトライします。「メール送信」ジョブが、送信完了直後にネットワークエラーで「失敗」と判定された場合、リトライによって同じメールが2通届きます。
# ❌ 危険: 冪等性がないジョブclass WelcomeEmailJob < ApplicationJob def perform(user_id) user = User.find(user_id) UserMailer.welcome_email(user).deliver_now # 問題: リトライされると、同じメールが複数回送信される endend改善案: 「既に送信済みフラグが立っていたら何もしない」といった、何度実行しても結果が変わらない(冪等な)設計を徹底しましょう。
# ✅ 安全: 冪等性のあるジョブ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) endendこの設計により、リトライされても同じメールが複数回送信されることを防げます。 ジョブの実行環境と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の高度な機能が使えない endend
# Sidekiqネイティブ(直接API)class PaymentJob include Sidekiq::Job
sidekiq_options queue: 'default', retry: 5, backtrace: true
def perform(order_id) # 利点: Sidekiqの全機能が使える # - バッチ処理 # - 詳細なリトライ制御 # - レートリミット endend改善案: 基本はActive Jobで書き、パフォーマンスや複雑な制御が必要になったらSidekiqネイティブな書き方を検討するという使い分けを意識しましょう。
| ケース | 使用する方法 | 理由 |
|---|---|---|
| 基本的な非同期処理 | Active Job | 共通規格で移植性が高い |
| バッチ処理が必要 | Sidekiq::Job | Sidekiqのバッチ機能が必要 |
| 詳細なリトライ制御 | Sidekiq::Job | リトライ戦略の細かい制御が必要 |
| レートリミットが必要 | Sidekiq::Job | Sidekiqのレートリミット機能が必要 |
Sidekiqの導入手順 Gemの追加: Gemfileに以下の行を追加し、bundle installを実行します。
Ruby
Gemfile
Section titled “Gemfile”gem ‘sidekiq’ アダプターの設定: config/application.rbに、Active JobのアダプターとしてSidekiqを設定します。
Ruby
config/application.rb
Section titled “config/application.rb”config.active_job.queue_adapter = :sidekiq Sidekiqの起動: Redisをインストールし、ターミナルで以下のコマンドを実行してSidekiqプロセスを起動します。
Bash
bundle exec sidekiq このように、Active JobとSidekiqを組み合わせることで、アプリケーションの要件に合わせて、スケーラブルで堅牢な非同期処理を実装することが可能になります。