使っている環境
- Vscode
- Rails 5.2.6
- ruby 2.6.6
目次
初めに
- 何か不手際があればご指摘いただけるとありがたいです。🙇
##1. どんな問題?
- テーブルにたくさんアクセスしてしまいデータが多いと重くなる問題
(図:ユーザーがたくさんの映画口コミができる。そして映画一覧の表示で映画に紐づいたユーザーの名前など表示)
##2. 具体的に中身を見ていく
- まず、scaffoldを利用し簡単な投稿アプリを作成
$ rails new movie_review
$ cd movie_review/
$ rails g scaffold Movie title:string body:text user_id:integer
$ rails g scaffold User name:string age:integer
- 後はモデルでのアソシエーション(図同様)を行い、movies/index.html.erbに以下の様に修正し
<% @movies.each do |movie| %>
<tr>
# 以下1行を追加
<td><%= movie.user.name %></td>
<td><%= movie.title %></td>
<td><%= movie.body %></td>
<td><%= link_to 'Show', movie %></td>
<td><%= link_to 'Edit', edit_movie_path(movie) %></td>
<td><%= link_to 'Destroy', movie, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
- 下準備完了したので、表示後のログを確認すると
11回アクセスしているのが分かります!今回は映画レビューは10個されているので”N+1”=10(映画レビューの個数)+1、つまり”N+1”の”N”は一覧にしているレコードの個数なので、仮に表示件数が1000となると重くなる。なるほどだからN+1”問題”なのか!
じゃあそうすればよいか、調べていくとなんと”N+1”回アクセスしてしまう問題という事だったので、Nに左右されない様にするにはNを1にしちゃう、つまり1回で済ませば良いみたい!すげぇえぇえ。
その為には”incudesメソッド”を使用するみたい。
##3. 解決方法
includesメソッドを使えば良いみたい
##4. includesメソッドとは
レファレンスを確認すると
Page.includes(:category)
# SELECT "pages".* FROM "pages"
# SELECT "categories".* FROM "categories" WHERE "categories"."id" IN (1)
こんな感じのSQL文が発行されるみたい
つまり、確かに一回のアクセスで一気に情報をとってますね。
自分のところでもコントローラーで使用すると
def index
#修正前
# @movies = Movie.all
#修正後
@movies = Movie.includes(:user)
end
再度映画一覧ページ(movies/index.html.erb)を確認すると、以下のようなログになりました。
うんうん、確かにすっきりしました。ただ、今回データが少ないのであんまり恩恵を感じないのでさらにデータを増やしてみたいと思います!
##5. データを増やしてみる
Userを100人分、Movieを1000本分用意しました。まず、allメソッド(アクセスが多い方)で時間を確認すると
時間の見方は良く分かりませんが、最後の1行をみると約10秒かかっていますね
次に、includesメソッドを使ったログを見ると
こちらは約0.6秒ですね。因みに、数値からもわかる様にデータを増やす前よりは増やしてからの方が体感でも恩恵を感じられました。
以前から聞いてたN+1問題とはこの事だったのですね。ストレスの原因になるのでとても重要ですね。
##6. まとめ
- N+1問題とは表示に時間がかかってしまう問題
- 原因は関連付けをそれぞれでアクセスしてしまっていた事
- 対策は一度のアクセスでデータを取ってくる→incudesメソッドを使おう!
反省
- ActiveRecorldにまかせっきりなので、SQL文があまり理解できていなかったので、勉強していかないといけません。。。
- 以下のような文をしっかり理解できていなかったので、時間計算も学ぶ必要があるなと感じました。読み込みなどを改善する際に原因を掴むにあたり重要かと思うので。
Completed 200 OK in 569ms (Views: 517.6ms | ActiveRecord: 12.0ms | Allocations: 136678)
- また、ネストの時もあるみたいなので、今後見ていきます!
参考
- incudesメソッドについて
Railsドキュメント(参照日時:2022/12/31) - 解決方法について
【Rails】 N+1問題をincludesメソッドで解決しよう!(参照日時:2022/12/31)