Tech Hotoke Blog

IT観音とは私のことです。

で、結局preload, eager_load, includesのどれを使うのがよろしいの? #Ruby on Rails #ActiveRecord

これは何?

preload, eager_load, includesを見かけるたびに、あれ、これってどういう挙動をするんだっけ?と調べているのでメモ。

前提

Tips

結論

  • eager_load
    • 1対1あるいはN対1のアソシエーションをJOINする場合に使う
  • preload
    • 多対多のアソシエーションの場合に使う
  • joins
    • メモリの使用量を必要最低限に抑えたい場合に使う
    • JOINした先のデータを参照せず、絞り込み結果だけが必要な場合に使う
  • includes
    • 使わない。私は断固として使わない。使ってるコードを見つけたら絶対に駆逐する。

Eager loading と Lazy loading

  • Eager loading

    • 予めメモリ上にActive Recordで情報を保持する方法。
    • ActiveRecordのメソッドで言えば、preload, eager_load, includesなど。
    • pros : 素早いレンダリングが可能になる。
    • cons : アソシエーションしているテーブルにある情報が膨大な場合、大量のメモリを消費することになる。
  • Lazy loading

    • JOINしたテーブルの情報が必要になった時に SQLを発行する方法。
    • ActiveRecordのメソッドで言えば、joinsなど。
    • pros : メモリを確保する量はEagerLoadingにくらべて少なくなる。
    • cons : JOINするテーブルを参照するたびSQLを発行するためWebサイト表示パフォーマンスを悪くする場合があります(N+1問題)

それぞれのメソッドの比較

メソッド クエリ アソシエーション先の参照 デメリット
eager_load LEFT JOIN できる JOIN先のデータ多いほど速度が低下する
preload SELECT できない データ量が多いと、IN句が肥大化してメモリを圧迫する
joins INNER JOIN できる N+1問題が発生するかも
incleds SELECT または LEFT JOIN できる 理解していないと予想外の挙動をするかも
  • どんな時に使うべきか??
    • eager_load
      • 1対1あるいはN対1のアソシエーションをJOINする場合。1回のSQLでまとめて取得した方が効率的な場合が多いと思うから。
    • preload
      • 多対多のアソシエーションの場合。データ量が多くなる場合が多いと思うので、使用するとパフォーマンスの向上につながるケースが多そうだから。
    • joins
      • メモリの使用量を必要最低限に抑えたい場合
      • JOINした先のデータを参照せず、絞り込み結果だけが必要な場合
    • includes
      • 使わない。eager_loadとpreloadを明示的に使い分けたほうが、思わぬボトルネックを埋め込まなくなると思うから。

【おまけ】includesの挙動について

  • includesしたテーブルでwhereによる絞り込みを行っている
  • includesしたassociationに対してjoinsかreferencesも呼んでいる
  • 任意のassociationに対してeager_loadも呼んでいる のうちいずれかに該当すると、eager_loadが呼ばれる

github.com

  • 例 下記の場合は、eager_loadの挙動をします。
app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  def index
    @articles = Article.includes(:reviews, :users).where(reviews: { article_id: 1})
  end
end

下記の場合は、preloadの挙動をします。

app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  def index
    @articles = Article.includes(:reviews, :users)
  end
end