Rails初学者がN+1問題を理解するまで
はじめに
現在、未経験からWebエンジニアを目指してRuby on Railsを学習しています。
ポートフォリオとして開発しているコーデ記録アプリ「Stylog」を作る中で、初めて「N+1問題」という言葉に出会いました。
最初は、
「includesを書けばいいらしい」
くらいの理解でしたが、実際に調べていく中で、
- なぜ問題なのか
- なぜincludesで解決できるのか
を少しずつ理解できるようになりました。
この記事では、Rails初学者の自分がN+1問題を理解するまでの過程をまとめます。
N+1問題とは?
例えば投稿一覧画面を作るとします。
投稿ごとに投稿者名を表示したい場合、
@posts = Post.all
ビューで
<% @posts.each do |post| %>
<%= post.user.username %>
<% end %>
と書けます。
一見問題なさそうですが、
実際には以下のようなSQLが発行されます。
SELECT * FROM posts;
SELECT * FROM users WHERE id = 1;
SELECT * FROM users WHERE id = 2;
SELECT * FROM users WHERE id = 3;
...
投稿数が100件なら、
1回
+
100回
=
101回
のSQLが発行されます。
これがN+1問題です。
なぜ問題なのか
データベースへのアクセスは比較的コストが高い処理です。
投稿数が少ないうちは気付きませんが、
データ量が増えるとページ表示速度に影響します。
つまり、
データが増える
↓
SQLが増える
↓
表示が遅くなる
という問題が発生します。
includesで解決する
Railsではincludesを利用できます。
@posts = Post.includes(:user)
これだけで、
Railsが事前にユーザー情報をまとめて取得してくれます。
発行されるSQLは、
SELECT * FROM posts;
SELECT * FROM users WHERE id IN (1,2,3,...);
のようになります。
結果として、
101回
↓
2回
まで減らせます。
Stylogで実際に使った例
自分のアプリでは投稿一覧で、
- 投稿者
- タグ
- いいね
- 画像
を同時に表示しています。
そのため、
@posts = Post.includes(
:likes,
:tags,
image_attachment: :blob,
user: [avatar_attachment: :blob]
).order(created_at: :desc)
としています。
最初は意味が分かりませんでしたが、
「必要なデータを先にまとめて取得している」
と理解すると少し分かりやすくなりました。
学んだこと
最初は、
includesを書けばOK
くらいの理解でした。
しかし調べていく中で、
なぜ書くのか
↓
SQLを減らすため
↓
表示速度改善につながる
ということが分かりました。
Railsは便利なので問題に気付きにくいですが、
パフォーマンスを意識する良いきっかけになりました。
まとめ
N+1問題を学んだことで、
単に機能を実装するだけでなく、
「その処理は効率的なのか」
を考えるようになりました。
まだまだ理解しきれていませんが、
Rails初学者としては大きな学びだったと思います。
もしRails学習中の方がいたら、
まずは
.includes
がなぜ必要なのかを調べてみることをおすすめします。
自分のアプリの見え方も変わってくると思います。
Discussion