🌐

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