0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

N+1問題とは?RailsのActiveRecordで理解する原因と解決策

0
Posted at

はじめに

Webアプリを開発していると、「N+1問題」という言葉を耳にすることがあります。
知らないうちに発生していることも多く、アプリが遅くなる原因の一つです。

この記事では、N+1問題がどういうものなのか、なぜ起きるのか、そしてどう解決するかを順番に説明したいと思います。


N+1問題とは

N+1問題とは、本来1回で済むはずのDBへの問い合わせが、N+1回も発生してしまうパフォーマンス上の問題です。

「N」はレコードの件数を指します。たとえば投稿が10件あれば、10+1=11回のSQLが発行されてしまう、ということです。


具体例で見てみる

テーブル構成

以下のような構成を例に考えます。

  • User(ユーザー)が複数のPost(投稿)を持つ
  • User has_many :posts / Post belongs_to :user

N+1が発生するコード

# コントローラー
def index
  @posts = Post.all
end
<%# ビュー %>
<% @posts.each do |post| %>
  <p><%= post.title %> - 投稿者:<%= post.user.name %></p>
<% end %>

一見、問題なさそうに見えます。しかし、このコードが実行されると裏側では以下のようなSQLが発行されています。

-- 全投稿を取得(1回)
SELECT * FROM posts;

-- 各投稿のユーザーを1件ずつ取得(N回)
SELECT * FROM users WHERE id = 1;
SELECT * FROM users WHERE id = 2;
SELECT * FROM users WHERE id = 3;
-- ...投稿の件数だけ繰り返される

投稿が100件あれば、SQLは101回発行されます。件数が増えるほど、アプリの動作はどんどん遅くなっていきます。


なぜ発生するのか

ActiveRecordは関連レコードを必要になったタイミングで都度取得する仕組みになっています(遅延読み込み/Lazy Loading)。

post.user.name のように関連先のデータにアクセスするたびに、その都度SQLを発行してしまうため、ループの中で関連データを参照するとN+1が起きやすくなります。


解決策:Eager Loading(事前読み込み)

N+1問題の解決策として、**Eager Loading(イーガーローディング)**があります。

これは関連するレコードをあらかじめまとめて取得しておく方法です。Railsでは includes メソッドを使って実現できます。

includes を使った修正

# コントローラー
def index
  @posts = Post.includes(:user).all
end

ビューはそのままで構いません。

これだけで、発行されるSQLが次のように変わります。

-- 全投稿を取得(1回)
SELECT * FROM posts;

-- 関連するユーザーをまとめて取得(1回)
SELECT * FROM users WHERE id IN (1, 2, 3, ...);

合計2回のSQLで済むようになりました。投稿が何件あっても、SQLの発行回数は変わりません。


includes 以外の方法

Eager Loadingには includes のほかにも方法があります。用途によって使い分けることがあります。

メソッド 特徴
includes 状況に応じてSQLの発行方法を自動で最適化してくれる。基本はこれを使えばOK
preload 必ず2回のSQLに分けて取得する
eager_load JOINを使って1回のSQLで取得する。whereで関連テーブルを条件に使いたいときに有効

迷ったときは includes を使っておけば問題ありません。


N+1が発生しているか確認する方法

ログを確認する

開発環境でRailsサーバーを起動しているターミナルのログを見ると、発行されているSQLが確認できます。同じようなSQLが何度も繰り返されていたら、N+1が起きているサインです。

bullet gemを使う

bullet というgemを使うと、N+1問題が発生しているときに自動で警告を出してくれます。開発中に導入しておくと便利です。

# Gemfile
gem 'bullet', group: :development

まとめ

N+1問題について整理したいと思います。

  • N+1問題とは、DBへの問い合わせが必要以上に多く発生するパフォーマンス問題
  • ループの中で関連レコードにアクセスするときに起きやすい
  • includes を使ったEager Loadingで解決できる
  • bulletなどのツールで検出できる

ActiveRecordは便利な反面、N+1問題のように意識しないと気づきにくい落とし穴もあります。ログを確認する習慣をつけることで、問題に早めに気づけるようになるはずです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?