Judeeeの自由帳

雑にまとめてく

ちゃんと理解する!RailsでのN+1問題💎

この記事は、「フィヨルドブートキャンプ Part 2 Advent Calendar 2023」10日目の記事です。

adventar.org

概要

N+1問題と聞いて、ちゃんと説明できる方はどれくらいいるでしょうか?

「あ〜なんかパフォーマンス落ちるやつでしょ」「Railsのメソッド使えば何かいい感じに解決できるでしょ」という認識で、上手く噛み砕けていない方もいるかと思います(私のことです)

本記事では、Railsのメソッドを使わずにN+1問題の解決を試みました。

わかっている方にとってはくどく感じるかもしれません🙇

本記事の流れ

本のタイトルと、それを所持しているユーザーの名前を一覧画面に表示させることを考えていきます。

テーブルは下図を想定します。

よく見るパターン

N+1問題でよく見る例を元に、紐解いていきましょう👀

# books_controller.rb
def index
  @books = Book.all 
end
# index.html.erb
<% @books.each do |book| %>
  <p><%= book.title %></p>
  <p><%= book.user.name %></p>
<% end %>

図で書くとこのようなイメージになります。

このように、クエリがレコード数分だけ発行されていることがわかります。

この例ではレコード数が少ないですが、レコード数に比例してパフォーマンスが落ちます。

他テーブルを参照しない形に変更する

他テーブルを参照する時にN+1問題が発生するので、これを解決できれば良さそうです!

つまり、該当するレコードを一度に一気に取得してみましょう💪

# books_controller.rb
  def index
    @books = Book.all
    user_ids = @books.map{|book| book.user_id}
    @users = User.where(id: user_ids)
  end
# index.html.erb
  <% @books.each do |book| %>
    <p><%= book.title %></p>
    <p><%= @users.find{|user| user.id = book.user_id}.name  %></p>
  <% end %>

図で書くとこのようなイメージになります。

SQLの発行は2回で済んでいることがわかると思います。

N+1を解決するメソッド

RailsにはN+1を解決するメソッドがいくつかあります。

本記事では詳細な説明を省きますが、実は今まで説明してきたことがRailspreloadメソッド一行で実現できます。

書き方は以下の通りです。

Book.all.preload(:user)

便利😎

参考:

api.rubyonrails.org

まとめ

Rails特有のメソッドを用いずにN+1について説明してきました。

初めてお絵描きソフトを使ったので、楽しくてポンチ絵をいっぱい書いてしまいました🖼️

少しでも理解の助けになれば幸いです!