RailsのN+1問題とは?クエリ最適化とincludes・eager_load・Bulletを初心者向けに徹底解説【ログの見方と再現例】
生徒
「Railsでアプリを作っていたら、動きがすごく遅くなりました。原因が分からなくて…」
先生
「画面は普通に表示されているけれど、裏側で無駄なデータ取得が大量に起きている可能性があります。」
生徒
「無駄なデータ取得って、どういうことですか?」
先生
「Railsでとても有名な“N+1問題”が起きているかもしれません。順番に見ていきましょう。」
1. Railsとは?まずは超かんたんな前提知識
Rails(Ruby on Rails)は、Webアプリケーションを作るための道具です。Webアプリとは、ブラウザで使えるサービスのことで、ブログやSNS、予約サイトなどが代表例です。
Railsでは、MVCという考え方を使います。これは役割分担のルールです。
- Model(モデル):データを扱う担当(データベースと話す)
- View(ビュー):画面を作る担当(見た目)
- Controller(コントローラ):全体の司令塔
今回のN+1問題は、主にModel(Active Record)が関係します。Active Recordとは、Rubyのコードでデータベースを操作できる仕組みのことです。
2. N+1問題とは?初心者向けにたとえ話で理解しよう
N+1問題とは、必要以上にデータベースへ問い合わせ(SQL)を送ってしまう問題です。
たとえば、先生が生徒名簿を配る場面を想像してください。
・最初に「クラスの生徒一覧」を取りに行く(1回)
・そのあと、生徒一人ひとりについて「この生徒の住所を教えて」と毎回聞きに行く(N回)
これで合計1 + N回の質問になります。これがN+1問題の名前の由来です。
Railsでも同じで、一覧を取得したあとに、関連データを1件ずつ取りに行くと、アプリがどんどん遅くなります。
3. N+1問題の再現例:Railsコードで見てみよう
ここでは「ユーザー」と「投稿」があるとします。1人のユーザーは、たくさんの投稿を持っています。
# controller
@users = User.all
<% @users.each do |user| %>
<p><%= user.name %></p>
<p><%= user.posts.count %></p>
<% end %>
一見、問題なさそうに見えます。しかし裏側では、ユーザー一覧を1回取得したあと、ユーザーごとに投稿を取りに行くSQLが発行されています。
これがN+1問題の典型例です。
4. Railsのログを見てN+1問題を発見する方法
Railsでは、ログという記録が自動で残ります。ログとは「裏側で何が起きたかの履歴」です。
ターミナルに同じようなSQLが何度も表示されていたら要注意です。
SELECT "users".* FROM "users"
SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = 1
SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = 2
SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = 3
このように、似たSQLが何回も出ていたら、N+1問題が起きています。
5. includesでN+1問題を解決する方法
includesは、関連データをまとめて先読みする命令です。
@users = User.includes(:posts)
これだけで、ユーザーと投稿をまとめて取得できます。結果として、SQLの回数が激減します。
初心者の方は、「あとで使いそうなデータを最初に一緒に取る」と覚えると分かりやすいです。
6. eager_loadとは?includesとの違いをやさしく説明
eager_loadもN+1対策の一つです。こちらは、必ずJOINを使って1回のSQLで取得します。
@users = User.eager_load(:posts)
JOINとは、複数の表を合体させて取ってくる方法です。
細かい違いは気にせず、includesで解決しないときの選択肢くらいの理解で大丈夫です。
7. Bulletとは?N+1問題を自動で教えてくれる便利ツール
Bulletは、N+1問題を見つけて教えてくれる便利なツールです。
初心者が自力でログを見て判断するのは大変なので、Bulletを使うと安心です。
gem 'bullet'
設定すると、画面やログで「ここN+1ですよ」と教えてくれます。
まるで先生が横で見てくれているような存在です。
8. N+1問題が起きる根本原因と考え方
N+1問題の原因は、とてもシンプルです。
「関連データを意識せずにコードを書いてしまうこと」です。
Railsは便利ですが、何も考えずに書くと裏側で大量のSQLが動きます。
「一覧表示+関連データ」を書いたら、N+1を疑うクセをつけることが大切です。
これが、Railsのクエリ最適化の第一歩になります。