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

Rails 7.2: strict_loadingがn_plus_one_onlyモードで子の関連付けをeager loadingしないよう修正(翻訳)

概要

元サイトの許諾を得て翻訳・公開いたします。

参考: 4.1.2.14 :strict_loading -- Active Record の関連付け - Railsガイド

日本語タイトルは内容に即したものにしました。
strict loading、eager loading、lazy loadingは原則として英ママとしました。

Rails 7.2: strict_loadingがn_plus_one_onlyモードで子の関連付けをeager loadingしないよう修正(翻訳)

Railsのstrict_loadingモードは、関連付けがlazy loading(遅延読み込み)されるのを防ぐためのものです。

strict_loadingモードを利用すると、関連付けられるレコードはincludesでeager loadingされなければならなくなります。さもないとActiveRecord::StrictLoadingViolationErrorエラーが発生します。

このstrict_loadingモードは、特定の関連付けをeager loadingすることでN+1クエリ問題を特定および修正し、パフォーマンスのボトルネックを回避するうえで有用です。

class Client < ApplicationRecord
  has_many :projects
end
Client.strict_loading.first.projects

# ActiveRecord::StrictLoadingViolationErrorが発生
class Client < ApplicationRecord
  has_many :projects, strict_loading: true
end
Client.first.projects

# ActiveRecord::StrictLoadingViolationErrorが発生
Client.includes(:projects).first.projects

  Client Load (0.7ms)  SELECT "clients".* FROM "clients" ORDER BY "clients"."id" ASC LIMIT $1  [["LIMIT", 1]]
  Project Load (2.4ms)  SELECT "projects".* FROM "projects" WHERE "projects"."client_id" = $1  [["client_id", 1]]

# =>
[#<Project:0x00000011133aaa48
  id: 1,
  client_id: 1,
  name: "Miru",
  description: "Time tracking">,
 #<Project:0x0000001111f150b0
  id: 2,
  client_id: 1,
  name: "Azure.com",
  description: "Cloud Computing">
]

🔗 改修前

:n_plus_one_onlyモードを指定したstrict_loadingは、ネステッド関連付けの奥深くにアクセスしたときに発生する可能性のあるパフォーマンス低下問題に対処する目的で設計されています。

これにより、関連付けを深くトラバースすることを制限しつつ関連付けを直接読み込めるようになり、潜在的なN+1クエリ問題や順序の不一致に関連する予期せぬ自体を防げるようになります。

client = Client.find(1)
client.strict_loading!(mode: :n_plus_one_only)
client.projects.first

# SELECT "projects".* FROM "projects" WHERE "projects"."client_id" = $1  [["client_id", 1]] -- non-deterministic order

ただし、ネステッド関連付けを深くたどるとエラーになります。

client.projects.first.timesheets # ActiveRecord::StrictLoadingViolationErrorが発生

🔗 改修後

改修後は、:n_plus_one_onlyモードを指定したstrict_loadingが、子の関連付けをeager loadingしないようになりました。

#48785の変更によって子の関連付けがeager loadingされなくなって意図通りに振る舞うようになり、firstlastなどのメソッドが呼び出されたときに順序が一定しない問題も防止されます。firstlast自体はN+1問題を発生しないので、子の関連付けを呼び出してもエラーは発生しません。

つまり、関連付けはeager loadingされますが、子の関連付けはlazy loadingされるということです。

client = Client.find(1)
client.strict_loading!(mode: :n_plus_one_only)
client.projects.first

# SELECT "projects".* FROM "projects" WHERE "projects"."client_id" = $1  [["client_id", 1]] -- non-deterministic order

client.projects.first.timesheets # no longer raises error

🔗 まとめ

strict loadingを利用すると、関連付けられたレコードがeager loadingされていない場合はActiveRecord::StrictLoadingViolationErrorが発生するので、関連付けがlazy loadingされることを防止できます。

ただしstrict_loading!(mode: :n_plus_one_only)を利用すると、関連付けはeager loadingされ、子の関連付けはlazy loadingされます。

関連記事

Rails 7.2: ActiveRecord::Base#pluckにハッシュ値を渡せるようになった(翻訳)

Rails 7.2: ActiveRecord::Core#inspectの修正とattributes_for_inspectの便利な使い方(翻訳)


CONTACT

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