この記事は、「フィヨルドブートキャンプ Part 2 Advent Calendar 2023」10日目の記事です。
概要
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を解決するメソッドがいくつかあります。
本記事では詳細な説明を省きますが、実は今まで説明してきたことがRailsのpreload
メソッド一行で実現できます。
書き方は以下の通りです。
Book.all.preload(:user)
便利😎
参考:
まとめ
Rails特有のメソッドを用いずにN+1について説明してきました。
初めてお絵描きソフトを使ったので、楽しくてポンチ絵をいっぱい書いてしまいました🖼️
少しでも理解の助けになれば幸いです!