【Rails】N+1問題をicludesで解決することについてまとめてみた
はじめに
現在、Railsについて絶賛学び中のコーダーです。学習の中で「N+1問題」という、名前からして難しそうなことが出てきたので、まとめてみようと思います。(間違いあったらごめんなさい)
N+1問題とは
データベースからデータを取得する際に、頻繁に発生するパフォーマンス系の問題です。
具体的には、アソシエーションを使用して、1つのオブジェクトに複数のオブジェクトが紐づいている場合に、(例として、ユーザーが複数の投稿を持っているとします。)ユーザーごとにその投稿を取得しようとすると、ユーザーの数だけデータベースへのクエリ(データベースへの処理要求)が発生します。
このことをN+1問題といいます。
N+1問題が発生すると、データベースからのデータ取得が非効率になり、アプリケーションのパフォーマンスが大きく低下してしまいます。
例のコードを用いて説明
- Userモデル
class User < ApplicationRecord
has_many :posts
end
- Postモデル
class Post < ApplicationRecord
belongs_to :user
end
上記のように、UserモデルにPostモデルがアソシエーションにより紐づいているとします。(1対多の関係)
投稿一覧ページなどで、全てのユーザーとユーザーに紐づいた投稿を取得する場合に、以下のようなコードを書くことができます。
class PostsController < ApplicationController
def index
@users = User.all
end
end
<% @users.each do |user| %>
<h1><%= user.name %>の投稿</h1>
<% user.posts.each do |post| %>
<p><%= post.title %></p>
<% end %>
<% end %>
上記のコードでは、各ユーザーの投稿を表示するたびに、そのユーザーに紐づく投稿データ(post)をデータベースから取得するクエリが実行されます。
よって、もしユーザーが10人いると仮定すると、クエリの回数は1(全ユーザー取得)+10(各ユーザーの投稿取得)=11回になり、N+1問題が発生します。
これを解決するのがincludesメソッドになります。
includesメソッドを使用した解決策
includesメソッドを使用することで、必要な全てのデータを一度にロードすることができます。
def index
@users = User.includes(:posts).all
end
end
上記のコードでは、Userをロードする時に、同時にPostも一緒にロードします。その結果、クエリの回数が、ユーザー全員取得と全ユーザーの投稿取得の2回になります。
これにより、クエリの回数を大きく減らすことができ、パフォーマンス低下を防ぐことができます。
最後に
他にも「N+1問題」の解決方法がありそうですし、モデル同士の関係が「1対多」だけでなく、「多対多」の時などにはどうなるのか、などなどまた色々調べてみようと思います。
Discussion