🔖

Rails 8.0 のデフォルトでは、ジョブがトランザクション内でスケジューリングされてしまう

に公開

こんにちは、terandard です。

先日 Rails 8.1 がリリースされましたね。
それに合わせて弊社のサービスを Rails 7.2 から 8.0 にアップデートしましたが、その際に発生した問題について共有します。

概要

Rails 7.2 で導入された新機能として、Active Record のトランザクション内でジョブのエンキューを行うと、そのトランザクションがコミットされた後にジョブがエンキューされるようになりました。

https://railsguides.jp/v8.0/7_2_release_notes.html#ジョブがトランザクション内でスケジューリングされないようになった
https://techracho.bpsinc.jp/hachi8833/2024_04_16/141213#1-1

この機能は Rails 7.2 ではデフォルトで有効になっていますが、Rails 8.0 ではこの動作がデフォルトで無効化されています。

従来の挙動に戻すには、 ActiveJob::Base.enqueue_after_transaction_commit = true を追加する必要があります。

class ApplicationJob < ActiveJob::Base
  self.enqueue_after_transaction_commit = true
end

なぜ変更されたのか

Rails 7.2 では ActiveJob::Base.enqueue_after_transaction_commit の設定値はシンボルで、:never:always:default の3つの値を取ることができました。

  • :never エンキューを遅延しない
  • :always エンキューを常に遅延する
  • :default キューアダプタで振る舞いを定義する

しかしこの設定では :default の挙動がキューアダプタに依存することになり、どちらの振る舞いをするのかが分かりづらいという問題がありました。

Actually, what adapters do set something else by default? I don't love the :default option being this mystery box that's dependent on the adapter. That just seems confusing. I think this setting should just either be true or false. Even the :always implies that there's a :sometimes, which is also just odd.
実際、デフォルトで何か別の設定を行うアダプターってあるの? :default オプションがアダプターに依存する謎の箱みたいな存在なのが気に入らない。混乱を招くだけだ。この設定は単純に true か false であるべきだと思う。 :always ですら :sometimes が存在することを暗示しているようで、これもまた奇妙だ。
https://github.com/rails/rails/pull/52659#issuecomment-2302422632

そこで以下の PR によって Rails 8.0 から設定値が真偽値に変更されました。
https://github.com/rails/rails/pull/53375

この時にデフォルト値が false に変更されたため、Rails 8.0 ではトランザクション内でジョブがスケジューリングされなくなりました。
この変更は Changelog に記載されておらず、Rails 8.0 のアップデート時に問題になる可能性があるので注意が必要です。
https://railsguides.jp/8_0_release_notes.html#active-job

デフォルト値に関しては議論が続いており、DHH も以下のように言及しています。

Yes, by default, all jobs should enqueue after the commit. And this should be a setting that Active Job controls. And it should also be downplayed as something people might want to change. I don't think people should change this. In fact, if we didn't have the history of going back and forth on it, I wouldn't even have made it a setting.
はい、デフォルトではすべてのジョブはコミット後にエンキューされるべきです。そしてこれは Active Job が制御する設定であるべきです。そして人々が変更したいと思うようなものとして軽視されるべきです。人々がこれを変更すべきだとは思いません。実際、これまでの歴史がなければ、私はこれを設定にすることすらしなかったでしょう。
https://github.com/rails/rails/pull/53375#issuecomment-2555694252

デフォルト値を true に戻す PR も提出されているので、今後の動向に注目したいところです。
https://github.com/rails/rails/pull/55788

ちなみに Rails 8.1 からシンボルでの設定は廃止されます。ただしこのリリースノートにもデフォルト値に関する記載はありません。

  • ActiveJob::Base.enqueue_after_transaction_commit:never:always:defaultを設定するサポートを削除。
  • 非推奨化されていたRails.application.config.active_job.enqueue_after_transaction_commitを削除。

https://railsguides.jp/v8.1/8_1_release_notes.html#active-job-削除されたもの

おわりに

Rails 7.2 で導入された Active Job のトランザクション内スケジューリング機能は便利ですが、Rails 8.0 ではデフォルトで無効化されているため、Rails 8.0 にアップデートする際は注意が必要です。
Rails 8.0 でこの機能を有効化したい場合は、 ActiveJob::Base.enqueue_after_transaction_commit = true を追加してください。

Kaigi on Rails 2025 でもこの話題について発表がありましたので、興味がある方はぜひご覧ください。
https://speakerdeck.com/alstrocrack/fei-tong-qi-jobwotransactionnei-de-hu-bunayo-jue-dui-nihu-bunayo

GitHubで編集を提案
Social PLUS Tech Blog

Discussion