ハッカソンで共同開発しているアプリで、いわゆる「N+1 問題」が発生していた。
ログを見ているとめちゃめちゃDBにアクセスしている。。
実際にN+1問題がでていたコード
Posts.controller.rb
@posts = Post.all
index.html.erb
<% @posts.each do |post| %>
<ul>
<li>ポストID:<%= post.id%> </li>
<li>投稿者:<%= link_to "#{post.user.name}", show_user_path(post.user)%></li>
<li>日付:<%= post.date%>曜日:<%= post.day_of_week %></li>
<li>時刻:<%= post.time %>時</li>
<li>方面:<%= post.direction%></li>
<li>駅:<%= post.station.station_name%></li>
<li>混雑具合:<%= post.congestion_level%></li>
<li>コメント:<%= post.comment%></li>
<%= button_to '投稿削除', delete_post_path(post.id), data: {confirm: '本当に投稿を削除しますか?'}, method: :delete, class: "btn btn-warning" %>
</ul>
<% end %>
UserとPostとStationテーブルがあり、関係は
User.rb
has_many :posts
Post.rb
belongs_to :user
belongs_to :station
となっている。
原因
コントローラーで Post.all である @posts をviewに渡して、それをeach文で回しながら、それぞれ関連づいている
- post.user
- post.station
などを表示させる仕組みだったが、それらの関連づいたデータを表示させる際に、一回一回DBにデータをとりに行っていた為に、DBへのアクセス回数が増え、いわゆる N+1問題が発生していた。
解決法
ググったらすぐに出てきた。
controllerで
Post.allでなく
Post.all.includes(:user, :station)
とするとPostに関連づいた User, Postも取得してくれて post.user などを表示させる際に一々DBにアクセスしなくなる。
上記のincludes(:user, :station)は
関連づいているモデルとの関係が 1:N なのか 1: N : 1なのか、とかで書き方変わるみたい。
詳しくは下記サイトにありました。
https://qiita.com/hirotakasasaki/items/e0be0b3fd7b0eb350327
countでも発生していた
思ったよりも簡単なコード記述で解決できるんだな。
と思っていたらログを見ると、まだちょっとアクセスする回数が多い。。
よくよく見ると、ユーザーの投稿回数を表示させる
user.post.count
でやっぱり都度、DBにアクセスしている。
これもググったらすぐに出てきた。
結論からいうと、
user.post.count
とすればいいだけだった。
参考サイト
https://qiita.com/awakia/items/60467b95f92bee9a469a
プログラミング初心者の自分にとって、「N+1問題っていうのがあるみたいだけど、そのうち調べてみよ」くらいに思っていたが、調べてみると、割と簡単な記述で回避できるんだなぁ。と思った。
includes以外にもN+1問題を解消する方法があるみたいなので、また更新しようと思います。