Rails: ジョブでループせずに個別のジョブを生成しよう(翻訳)

概要

原著者の許諾を得て翻訳・公開いたします。

Rails: ジョブでループせずに個別のジョブを生成しよう(翻訳)

アプリの遅い処理や本質的でない処理は、できるだけ非同期ジョブに落とし込む方がアプリ全体のパフォーマンス向上に有効です。

以下のように書くのではなく

1つのジョブでオブジェクトのグループをイテレートし、繰り返しごとに何らかの処理を行う。

class DoABunchOfTranslationsJob < ApplicationJob
  def perform
    Text.find_each do |text|
      text.do_a_slow_translation
    end
  end
end

以下のように書く

最初に「キューに積む」ジョブを使って小規模な個別のジョブを多数作成し、個別のオブジェクトはそれらのジョブ内で処理する。

class DoABunchOfTranslationsJob < ApplicationJob
  Text.find_each do |text|
    DoASingleTranslationJob.perform_later(text)
  end
end

class DoASingleTranslationJob < ApplicationJob
  def perform_later(text) 
    text.do_a_slow_translation
  end
end

そうする理由

理想的には、ジョブをできる限り速やかに実行し、バックグラウンドのコンカレンシーを活用すべきです。

ジョブが失敗する理由はさまざまです。ジョブ自身の中でエラーがraiseされることもあれば、ジョブに関連する外部の環境(再起動や何らかのシステムエラーなど)でエラーになることもあります。

ジョブが長時間実行されると、ジョブの実行中に中断する可能性が高まります。そうしたタスクでは大量のメモリも使われるでしょう。

実行に時間のかかるジョブでエラーが発生すると、2つの問題が発生します。作業中のステートに不整合が生じたままになることと、そのジョブを最初からやり直さなければならなくなることです。つまり、ジョブが失敗すると、一部の処理がもう一度(おそらく2回以上)行われてしまうことになります。場合によっては、ジョブがいつまでたっても終了できなくなるかもしれません。

処理を分割して、繰り返しても大丈夫な小規模な複数の処理に分けることで、個別のジョブも全体の「タスク」もより柔軟になります。

こうすることで、コードの完了が早まるというメリットも得られます。すべての処理を順に実行するのではなく、細かな「チャンク」に分割することでコンカレンシー性が高まり、多数のジョブを同時に実行できるようになります。

そうしない理由があるとすれば

間接的な制御レベルが1つ増えます。最終的に、ジョブをキューに積むあらゆるタスクで個別のジョブを作成することになります。つまりコードがその分複雑になり、後で見返したときに混乱しやすくなるかもしれません。

短時間で終わるループに余分な複雑さを持ち込むのはオーバーキルになる可能性があります。

関連記事

Rails tips: あまり知られてない機能1: ActiveJobとActiveModel(翻訳)

デザインも頼めるシステム開発会社をお探しならBPS株式会社までどうぞ 開発エンジニア積極採用中です! Ruby on Rails の開発なら実績豊富なBPS

この記事の著者

hachi8833

Twitter: @hachi8833、GitHub: @hachi8833 コボラー、ITコンサル、ローカライズ業界、Rails開発を経てTechRachoの編集・記事作成を担当。 これまでにRuby on Rails チュートリアル第2版の半分ほど、Railsガイドの初期翻訳ではほぼすべてを翻訳。その後も折に触れてそれぞれ一部を翻訳。 かと思うと、正規表現の粋を尽くした日本語エラーチェックサービス enno.jpを運営。 実は最近Go言語が好き。 仕事に関係ないすっとこブログ「あけてくれ」は2000年頃から多少の中断をはさんで継続、現在はnote.muに移転。

hachi8833の書いた記事

週刊Railsウォッチ

インフラ

ActiveSupport探訪シリーズ