カテゴリー: BackEnd

Railsでの非同期処理とDelayed Job

はじめに

大量なデータのインポートやメールの送信など、処理時間が長くなるタスクを実行する際は非同期で実行することが多いと思います。RailsではActive Jobという便利な仕組みにより、非同期処理を簡単に実装することができます。

Active Job単体でも使用することはできますが、プロセスがクラッシュしたりコンピュータをリセットしたりするとジョブが失われてしまいます。そのため、production環境では後に紹介するDelayed JobやSidekiqなどのライブラリと合わせて使用することが一般的です。

先日、業務でDelayed Jobを使う機会がありましたので、今回はActive Jobの基本的な説明と、バックエンドでジョブを実行するためのライブラリの一つであるDelayed Jobを紹介します。

Active Job

Active Jobは、ジョブを宣言し、それによってバックエンドでさまざまな方法によるキュー操作を実行するためのフレームワークです。

大量なデータのインポートやメールの送信など、様々な処理を非同期で並列的に実行できます。より詳しい説明はこちらから見ることができます。

Active Jobの役割

Active Jobの主な役割はジョブの処理とジョブ管理機能の制御を分離することです。これにより、ジョブの処理はジョブを実行する(SidekiqやDelayed Jobなど)のキューを管理するライブラリを意識する必要がなくなり、ジョブ管理機能ではキューの操作方法以外のことを気にする必要がなくなります。

さらに、ジョブごとに複数のキューを管理するライブラリを採用することができ、それらのライブラリを切り替える際にコードを書き換える必要がなくなります。

ジョブを作成する

ジョブはコントローラやモデルなどと同じようにRailsジェネレータで生成することができます。以下のコマンドを実行するとapp/jobsにジョブが生成されます。

$ bin/rails g job mail_delivery
create  app/jobs/mail_delivery_job.rb

生成されたコードは以下のとおりです。

class MailDeliveryJob < ApplicationJob
  queue_as :default

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

ジョブをキューに登録する

キューへのジョブの登録は以下のように行います。

# 「キューイングシステムが空いたらジョブを実行する」とキューに登録する
MailDeliveryJob.perform_later mail

# 明日正午に実行したいジョブをキューに登録する
MailDeliveryJob.set(wait_until: Date.tomorrow.noon).perform_later(mail) 

# 一週間後に実行したいジョブをキューに登録する
MailDeliveryJob.set(wait: 1.week).perform_later(mail)

また、ジョブに引数を渡す場合は以下のように行います。

# `perform_now`と`perform_later`は`perform`を呼び出すので、
# 定義した引数を渡すことができる
MailDeliveryJob.perform_later(mail_to, title)

コールバック

Active Jobが提供するフックを用いて、以下のようにジョブのライフサイクル中に任意の処理を実行することができます。

class MailDeliveryJob < ApplicationJob
  queue_as :default

  def perform(*args)
    # Do something later
  end

  private
  def around_cleanup(job)
    # performの直前に何か実行
    yield
    # performの直後に何か実行
  end
end

利用できるコールバックは以下のとおりです。

  • before_enqueue
  • around_enqueue
  • after_enqueue
  • before_perform
  • around_perform
  • after_perform

例外

ジョブの実行中に起こった例外は以下のようにキャッチする事ができます。

class MailDeliveryJob < ApplicationJob
  queue_as :default
 
  rescue_from(ActiveRecord::RecordNotFound) do |exception|
   # ここに例外処理を書く
  end

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

例外が発生したときに以下のようにジョブのリトライや破棄も行なえます。

class MailDeliveryJob < ApplicationJob
  queue_as :default

  retry_on CustomAppException # defaults to 3s wait, 5 attempts
 
  discard_on ActiveJob::DeserializationError

  def perform(*args)
    # CustomAppExceptionかActiveJob::DeserializationErrorをraiseする可能性があるとする
  end
end

Delayed Job

Delayed Jobはジョブを実行するためのライブラリの一つです。

設定

まず、以下のGemをGemfileに追加してbundle installします。

gem 'delayed_job_active_record'

次に、ジョブのキューを保存するためのテーブルを作ります。

$ bin/rails generate delayed_job:active_record
$ bin/rake db:migrate

実行すると下記のテーブルが作られます。

mysql> desc delayed_jobs;
+------------+--------------+------+-----+---------+----------------+
| Field      | Type         | Null | Key | Default | Extra          |
+------------+--------------+------+-----+---------+----------------+
| id         | bigint(20)   | NO   | PRI | NULL    | auto_increment |
| priority   | int(11)      | NO   | MUL | 0       |                |
| attempts   | int(11)      | NO   |     | 0       |                |
| handler    | text         | NO   |     | NULL    |                |
| last_error | text         | YES  |     | NULL    |                |
| run_at     | datetime     | YES  |     | NULL    |                |
| locked_at  | datetime     | YES  |     | NULL    |                |
| failed_at  | datetime     | YES  |     | NULL    |                |
| locked_by  | varchar(255) | YES  |     | NULL    |                |
| queue      | varchar(255) | YES  |     | NULL    |                |
| created_at | datetime     | YES  |     | NULL    |                |
| updated_at | datetime     | YES  |     | NULL    |                |
+------------+--------------+------+-----+---------+----------------+

最後に、Active Jobと連携させるための設定をapplication.rbに追加します。

config.active_job.queue_adapter = :delayed_job

ワーカーの起動

Delayed Jobを設定しただけではジョブを処理することはできません。以下のコマンドでワーカーを起動させます。

$ bin/rake jobs:work

その他のライブラリとの比較

Delayed Jobの他によく使われるライブラリとしてはSidekiqやResqueなどがあります。Delayed JobとこれらのGemの大きな違いとしては、Delayed Jobがキューの管理にDBを用いるのに対して、これら2つはRedisで管理します。Delayed JobはRedisを使用しないため容易に利用できますが、ジョブの処理に時間がかかります。上記の理由により、非同期処理を多用するサイトではSidekiqやResqueなどを使ったほうが良いでしょう。

さいごに

Railsでの非同期処理とライブラリについて紹介しました。Active Jobは非常に使いやすいので、重たい処理は非同期で処理してユーザビリティを高めていきましょう。

参考

おすすめ書籍

        

Hiroki Ono

シェア
執筆者:
Hiroki Ono
タグ: Rails

最近の投稿

SWRを使ってみた①

はじめに 最近、React用ラ…

24時間 前

Goの抽象構文木でコードを解析する

はじめに Goでアプリケーショ…

1週間 前

フロントエンドで動画デコレーション&レンダリング

はじめに 今回は、以下のように…

1か月 前

Goのクエリビルダー goqu を使ってみる

はじめに 最近携わっているとあ…

2か月 前

【Xcode15】プライバシーマニフェスト対応に備えて

はじめに こんにちは、suzu…

2か月 前