1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Rails】N+1問題とその解決方法

Last updated at Posted at 2021-03-19

#はじめに
本記事ではRailsのアプリケーションを作成した際にN+1問題に直面したので、N+1問題の概要とその解決方法を紹介します。

#N+1問題とは?
N+1問題とはループ処理を行う際にSQLの発行がたくさん行われてしまい、サイトのパフォーマンスが下がってしまう現象です。

SQLの発行はサーバーの負担が大きいので、なるべく少ない回数にすることが望まれます。

N+1問題の例

わかりやすくするために、以下のような記事を投稿できるアプリケーションを例にします。

usersテーブル

id name
1 佐藤
2 鈴木
3 高橋
4 田中
5 伊藤

postsテーブル

id user_id text
1 3 こんにちは
2 4 おはよう
3 1 おやすみなさい
4 5 ありがとう
5 1 さよなら
6 2 もしもし
7 4 楽しい
8 1 悲しい

一人のユーザーからたくさんの投稿があるため、userモデルとpostモデルは以下のようになります。

user.rb
class User < ApplicationRecord
  has_many :posts, dependent: :destroy
end
post.rb
class Post < ApplicationRecord
  belongs_to :user
end

またposts_controllerは以下のようにします。

app/controllers/posts_controller.rb
class PostsController < ApplicationController
    def index
        @posts = Post.all
    end

    def new
        @post = Post.new
    end

    def create
        Post.create(post_params)
    end

    private
    def post_params
        params.require(:post).permit(:text)
    end
end

viewは全ての投稿の投稿者名と投稿内容を表示するようにします。

app/views/posts/index.html.erb
<% @post.each do |post| %>
  <p><%= post.user.name %></p>
  <p><%= post.text %></p>
<% end %>

このコードの実行ログは以下のようになります。

実行ログ

Post Load (1.1ms)  SELECT `posts`.* FROM `posts`
  User Load (1.3ms)  SELECT  `users`.* FROM `users` WHERE `users`.`id` = 3 LIMIT 1
  User Load (0.2ms)  SELECT  `users`.* FROM `users` WHERE `users`.`id` = 4 LIMIT 1
  User Load (3.1ms)  SELECT  `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
  User Load (1.2ms)  SELECT  `users`.* FROM `users` WHERE `users`.`id` = 5 LIMIT 1
  User Load (3.3ms)  SELECT  `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
  User Load (0.5ms)  SELECT  `users`.* FROM `users` WHERE `users`.`id` = 2 LIMIT 1
  User Load (0.3ms)  SELECT  `users`.* FROM `users` WHERE `users`.`id` = 4 LIMIT 1
  User Load (0.7ms)  SELECT  `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1

はじめにpostsテーブルに1回SQLが走り、その後usersテーブルに記事数である8回SQLが走っています。

今回は記事数がそれほど多くないのでサイトパフォーマンスはあまり下がりませんが、記事数が大量になっていくとサイトパフォーマンスが大幅に低下して、ユーザーが使いにくいアプリケーションになってしまいます。

#N+1問題の解決方法
今回はincludesメソッドを使用して解決します。

方法は簡単で、controllerのindexアクションにincludesメソッドを追加するだけです。

app/controllers/posts_controller.rb
class PostsController < ApplicationController
    def index
        #indexアクションにincludesメソッドを追加
        @posts = Post.includes(:user)
    end

    def new
        @post = Post.new
    end

    def create
        Post.create(post_params)
    end

    private
    def post_params
        params.require(:post).permit(:text)
    end
end

このようにすると、実行ログは以下のようになります。

Post Load (0.3ms)  SELECT `posts`.* FROM `posts`
User Load (0.3ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` IN (5, 1, 3, 4, 2)

先ほど9回SQLを発行していましたが、今回は2回で完了しました。

これはcontroller側でincludesメソッドを使うことで、すでにpostsテーブルのレコードに関連するusersテーブルのレコードが取得されているからです。

#まとめ

  • n+1問題はSQLが大量に発行されてしまい、サイトのパフォーマンスが下がってしまう事である。
  • controller側でincludesメソッドを使うことで解消できる

アプリケーションではレスポンス速度を少しでも短くすることが大事なので、Railsエンジニアを目指している方はぜひ覚えておきましょう。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?