【Rails】N+1問題 （Bullet で検出し includes して対策）

# はじめに
未経験からWeb系エンジニアへの転職を目指し、ポートフォリオ（投稿アプリ）を作成中です。
今回は、ポートフォリオにN+1問題を対応させてみたので、まとめてみました！

# 前提
- Ruby2.7.0
- Ruby on Rails6.0.3
- MySQL8.0

# N+1問題とは
- 必要以上にSQLの処理が行われて、システムのパフォーマンスが低下してしまうこと。
- レスポンスが遅く、ユーザビリティの低いサービスの原因の多くは、SQL周りにあることが多いらしい。
- SQLの発行回数を減らし、パフォーマンスを上げることが大事。

# N+1問題の検出
まずは、N+1問題を検出するためBulletというgemを試してみる。

```ruby:Gemfile
group :development, :test do
  gem 'bullet'
end
```

```ruby:ターミナル 
＄ bundle
```

Bulletは` config/environments/development.rb`に設定を追加しないと動作しないのでファイルを編集する。

```ruby:config/environments/development.rb
Rails.application.configure do
  config.after_initialize do
    Bullet.enable = true # Bulletを有効化
    Bullet.alert = true # JavaScriptのポップアップアラートを有効化
    Bullet.bullet_logger = true # Rails.root/log/bullet.logに出力
    Bullet.console = true # ブラウザのconsole.logに出力
    Bullet.rails_logger = true # Railsのログに結果を出力
    Bullet.add_footer = true # ページの左下に結果を表示
  end
end
```
# いざ、確認！
ローカル環境で投稿一覧画面にアクセスしてみると、N+1問題が検出され、この問題を解決するためには` .includes([:user])`を追加すればよいとある。

```ruby:ターミナル 
user: hako
GET /posts/index
USE eager loading detected
  Post => [:user]
  Add to your query: .includes([:user])
Call stack
  /Users/hako/portfolio/cafe_app/app/views/posts/index.html.erb:48:in `block in _app_views_posts_index_html_erb___2990704399900080110_90940'
  /Users/hako/portfolio/cafe_app/app/views/posts/index.html.erb:5:in `_app_views_posts_index_html_erb___2990704399900080110_90940'
```

# N+1問題の対策
そこで、` app/controllers/posts_controller.rb`に` .includes([:user])`を追加してみる。

```ruby:app/controllers/posts_controller.rb
class PostsController < ApplicationController
  def index
    # @posts = Post.page(params[:page]).per(6).order(:created_at => :desc) # 編集前
    @posts = Post.includes([:user]).page(params[:page]).per(6).order(:created_at => :desc) # 編集後
  end
end
```

再アクセスしてみるとBulletによる警告が表示されなくなった！
今度は投稿へのコメントにアクセスしてみると、以下のメッセージが表示されたので、

```ruby:ターミナル 
user: hako
GET /posts/48
USE eager loading detected
  Comment => [:user]
  Add to your query: .includes([:user])
Call stack
  /Users/hako/portfolio/cafe_app/app/views/comments/_comment.html.erb:3:in `block in _app_views_comments__comment_html_erb__2537338689005093815_102800'
  /Users/hako/portfolio/cafe_app/app/views/comments/_comment.html.erb:2:in `_app_views_comments__comment_html_erb__2537338689005093815_102800'
  /Users/hako/portfolio/cafe_app/app/views/posts/show.html.erb:65:in `_app_views_posts_show_html_erb__3096142757258343957_102780'
```

同様に、` .includes([:user])`を追加。

```ruby:app/controllers/posts_controller.rb
class PostsController < ApplicationController
  def show
    @user = @post.user
    # @comments = @post.comments # 編集前
    @comments = @post.comments.includes([:user]) # 編集後
    @comment = @post.comments.build
  end
end
```

N+1問題が検出されなくなるまで、メッセージが表示される箇所に一つずつincludesを追加。
なお、Bulletを使っている中でN+1の警告を無視したい場合は、ホワイトリスト形式で` config/initializer/bullet.rb`に登録しておけば良いらしい。
今後いろいろ試してみたい。

# 終わりに
以上の対策をした後、本番環境にアクセスしてみるとパフォーマンスが上がってびっくり！動作が早い早い！！
ユーザー側に立つと、パフォーマンスが遅いとそれだけでストレスだし、そのサービスを使いたいと思えないもの。
今後は、SQLの実行回数をいかに減らせるかを意識してコードを書いていこうと思う！
引き続き、SQL周りの知識も深めていきたい。

初学者のため、理解不足な点が多々あると思います。気になった点やアドバイスなどありましたら、教えていただけますと幸いです！

# 参考サイト
・[ポートフォリオのバージョンアップ2020 7/2 (N + 1 問題)](https://www.wantedly.com/users/138516195/post_articles/252721)

・[Railsメモ(19) : BulletでN+1問題を検出する](https://wonderwall.hatenablog.com/entry/2015/08/16/171622)

・[N+1問題を発見しDBのクエリを改善するBullet](https://morizyun.github.io/ruby/library-bullet.html)