N+1問題(include,preload,eagar_load)
今回は、N+1問題について勉強していきたいと思います。
N+1問題とは
N+1問題とは、はじめの1回のSQLでModelを取得し、そのModelに対するデータ数分(N回)のSQLが実行されてしまう状態のことを言う。
順番から考えると、N+1問題というよりは1+N問題と呼んだ方がイメージしやすいかと思います。
また、N+1問題を検知するgemで「bullet」というものがある。.preload、eager_load、includeを追加しなくても勝手にN+1問題を解決してくれる優れもの。
allを使った場合とincludeを使った場合を比較してN+1問題を理解してみましょう。
allを使用した場合
Completed 200 OK in 204ms (Views: 100.3ms | ActiveRecord: 22.8ms | Allocations: 29218)
includeを使用した場合
Completed 200 OK in 158ms (Views: 69.6ms | ActiveRecord: 68.7ms | Allocations: 23876)
includeとallを比較してみると、includeの方がviewにレンダリングする時間が短いですね。includeを使用すると事前にActiverecordからデータをひっぱってきて、その後にviewにレンダリングするので、viewsに表示させるときにかかる時間が短いようです。
ちなみにN+1問題を解決させる方法は他にもあるみたいなので、まとめておきます。
preloadとeager_load,includeの使い分け
includes
はなるべく利用しない方が良い- 理由:意図しない挙動を防ぐため
- 代わりに、
preload
かeager_load
を使う
preload
- どんな場合に使うといいか : 多対多のアソシエーションの場合
- できないこと : アソシエーション先のデータ参照(Whereによる絞り込みなど)
- 注意 : データ量が大きいと、IN句が大きくなりがちで、メモリを圧迫する可能性がある
eager_load
はどんな場合に使うといいか- 1対1あるいはN対1のアソシエーションをJOINする場合(belongs_to, has_one アソシエーション)
- JOINした先のテーブルの情報を参照したい場合(Whereによる絞り込みなど)
joins
はどんな場合に使うか- メモリの使用量を必要最低限に抑えたい場合
- JOINした先のデータを参照せず、絞り込み結果だけが必要な場合
- 逆に言うと、引用先のデータを参照しない場合、使用しないほうがいいです
対Nのアソシエーションの場合はpreload
データ量が増えるほど、eager_load
よりも、preload
の方がSQLを分割して取得するため、レスポンスタイムは早くなる。
対1のアソシエーションの場合はeager_load
データ量が増えても、1回のSQLでまとめて取得した方が効率的な場合が多い。
ケースに応じて使い分けることができるようになったら、熟練者って感じがしますね。