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を一度に取得)で必要なすべてのデータが取れる。
preloadとeager_load
includes以外にもpreloadやeager_loadメソッドがある。
preload: 関連するレコードを別々のクエリでロードする。eager_load: 関連するレコードをLEFT OUTER JOINでロードする。
# preloadを使用する例
users = User.preload(:posts)
# eager_loadを使用する例
users = User.eager_load(:posts)
bullet gem
bulletをインストールすると、N+1問題が発生する可能性があるコードを検出してくれる。
ただし、完璧ではないので自分で確認するべき。