概要
原著者の許諾を得て翻訳・公開いたします。
- 英語記事: Don’t Loop & Do Work in Jobs - Andy Croll
- 原文公開日: 2018/09/30
- 著者: Andy Croll
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つ増えます。最終的に、ジョブをキューに積むあらゆるタスクで個別のジョブを作成することになります。つまりコードがその分複雑になり、後で見返したときに混乱しやすくなるかもしれません。
短時間で終わるループに余分な複雑さを持ち込むのはオーバーキルになる可能性があります。
おたより発掘
メモリリークにも効果があるので、おすすめ。
— 村野|Webエンジニア × グロースハック (@samemuramura) November 17, 2018