問題提起
通常、Railsを使って、N+1問題を解決しつつ、子要素以降にorderを指定しようとすると
Parent.eager_load(:children).order(:name, "children.name")
のように書く形になることが多いと思う。
今回はこの問題に関して本当にこれが正しいのか?を考えてみたい
例えば、下記のようなコードで利用することを考える
parents = Parent.eager_load(:children).order(:name, "children.name")
parents.each do | parent |
# ここでparentのオブジェクトに対する処理を行う
p parent
parents.children.each do | child |
# ここでchildのオブジェクトに対する処理を行う
p child
end
end
上記のような利用シーンでは、ParentにChildのデータがJOINされている意味
はなく、
parent.children
で、希望通りの並び順でchildが取得できていればいい
だけ。
変更案1
- children取得後に並べ替える
- orderから"children.name"を削除
- joinする必要がなくなるので、eager_loadからpreloadに変更
parents = Parent.preload(:children).order(:name)
parents.each do | parent |
# ここでparentのオブジェクトに対する処理を行う
p parent
parents.children.sort{ |a, b| a.name <=> b.name }.each do | child |
# ここでchildのオブジェクトに対する処理を行う
p child
end
end
ここで、気をつけてほしいのは、parent.children.order(:name)
としてしまうと、せっかくpreloadしたのものが使われず、もう1度データ取得されてしまうこと。
※sortメソッドでやるのも無理やり感ありますが。
変更案2
- 最初からsortしたアソシエーションを用意してそれを利用する
- joinは不要になるので、eager_loadをpreloadにする
Parentクラスに、下記のようなアソシエーションを追加する(既存のものを変えてもいい場合は変更したほうが簡単です)
has_many :sorted_children, class_name: "Child", ->{ order(:name) }
呼び出し部分を変える
parents = Parent.preload(:sorted_children).order(:name)
parents.each do | parent |
# ここでparentのオブジェクトに対する処理を行う
p parent
parents.sorted_children.each do | child |
# ここでchildのオブジェクトに対する処理を行う
p child
end
end
JOINの何が悪いのか
データ量が増えれば増えるほど、JOINのコスト、sortのコストは増加していきます。
パフォーマンスを考えた場合、不要なJOINをしないこと
が一番のパフォーマンスチューニングであると考えているので、なるべくJOINを使わない方法を探しました。
まとめ
個人的には変更案2が無難かなとは考えています。
他にもいい方法があるようであれば、教えてもらいたいです。