Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

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(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。