RailsのN+1問題について

N+1問題とは?

N+1問題とは、データベースからデータを取得する際に不必要に多くのクエリが発行されるという問題。

例えば、Userモデルがあり、それに紐づくPostモデルがあるとする。

class User < ApplicationRecord
  has_many :posts
end

class Post < ApplicationRecord
  belongs_to :user
end

次に、すべてのユーザーとその投稿を表示するコードを考える。

users = User.all

users.each do |user|
  puts user.name
  user.posts.each do |post|
    puts post.title
  end
end

このコードは、1回のクエリでusersを取得し、その後各ユーザーに対して別のクエリを発行してpostsを取得する。もしusersテーブルに100人のユーザーがいれば、このコードは101回のクエリを発行する(1回はUser.all、100回は各user.posts)。

  • パフォーマンスの低下: クエリが多いと、その分データベースへのアクセス時間が増える。
  • リソースの浪費: 不必要に多くのクエリを発行することで、データベースサーバーに負荷をかける。

解決方法

includes

Railsで簡単にN+1問題を解決する方法の一つは、includesメソッドを使うこと。

users = User.includes(:posts)

users.each do |user|
  puts user.name
  user.posts.each do |post|
    puts post.title
  end
end

このコードでは、2回のクエリ(1回はusersを取得、もう1回は関連するpostsを一度に取得)で必要なすべてのデータが取れる。

preloadeager_load

includes以外にもpreloadeager_loadメソッドがある。

  • preload: 関連するレコードを別々のクエリでロードする。
  • eager_load: 関連するレコードをLEFT OUTER JOINでロードする。
# preloadを使用する例
users = User.preload(:posts)

# eager_loadを使用する例
users = User.eager_load(:posts)

bullet gem

bulletをインストールすると、N+1問題が発生する可能性があるコードを検出してくれる。
ただし、完璧ではないので自分で確認するべき。