RailsモデルとActive Record基礎|N+1問題を避けるためのincludes・eager_load・preloadとBulletの使い方
生徒
「Railsで画面を表示しただけなのに、動きがすごく遅くなることがあるんですが、なぜですか?」
先生
「それはN+1問題が原因かもしれません。Rails初心者が必ず一度はつまずくポイントです。」
生徒
「N+1って数字みたいで、まったくイメージできません……」
先生
「大丈夫です。買い物や配達の例えで、ゆっくり説明していきましょう。」
1. N+1問題とは?Rails初心者が最初に知るべき落とし穴
N+1問題とは、RailsやActive Recordを使ったときに、 データベースへのアクセス回数が必要以上に増えてしまう問題です。 プログラム自体は正しく動いているのに、 画面表示が遅くなる原因になります。
例えば、10件の記事を表示し、それぞれに投稿者名を表示する場合を考えてみてください。 記事を1回取得し、そのあと投稿者を10回取得すると、 合計で11回もデータベースに問い合わせることになります。
これが「N件 + 追加で1件」のように増えていくため、 N+1問題と呼ばれています。 初心者の方は、「1つずつお店に買い物に行く」イメージを持つと分かりやすいです。
2. N+1問題が起きる基本的なコード例
Railsでは、モデル同士の関連(アソシエーション)を使うと、 とても自然な書き方ができます。 しかし、この便利さがN+1問題を生みやすくします。
posts = Post.all
posts.each do |post|
puts post.user.name
end
このコードは一見シンプルですが、 記事を取得したあと、ループの中で毎回ユーザーを取得しています。 データが増えるほど、動作がどんどん遅くなります。
3. includesとは?まとめて読み込む基本テクニック
N+1問題を避けるために、Railsでは
includesという便利な仕組みが用意されています。
これは「必要なデータを最初にまとめて取っておく」方法です。
例えるなら、1件ずつ配達を頼むのではなく、 まとめて段ボールで届くイメージです。
posts = Post.includes(:user)
posts.each do |post|
puts post.user.name
end
includesを使うことで、 Railsが自動的に最適な方法でデータを取得してくれます。 初心者の方は、まず「N+1を見たらincludes」 と覚えておくと安心です。
4. preloadとeager_loadの違いをやさしく理解する
includesの仲間として、
preloadとeager_loadがあります。
名前は難しそうですが、考え方はシンプルです。
preloadは、「別々にまとめて取る」方法です。 eager_loadは、「最初から結合して一気に取る」方法になります。
Post.preload(:user)
Post.eager_load(:user)
普段はincludesを使えば十分ですが、 条件検索や並び替えを関連先で行う場合は、 eager_loadが必要になることがあります。 最初は深く考えすぎなくても大丈夫です。
5. Bulletとは?N+1問題を教えてくれる先生役
N+1問題は、見た目だけでは気づきにくいのが厄介な点です。 そこで役立つのがBulletというツールです。
Bulletは、N+1問題が発生したときに、 「ここで無駄な読み込みがありますよ」と 教えてくれる見張り役のような存在です。
gem 'bullet'
開発中にBulletを使うことで、 初心者でもパフォーマンスの悪化に気づきやすくなります。 Railsで長く開発するなら、早めに慣れておくと安心です。
6. N+1を避ける意識を身につけることが大切
N+1問題は、RailsやActive Recordを使う以上、 避けて通れないテーマです。 ですが、仕組みを理解すれば怖いものではありません。
「ループの中で関連データを呼んでいないか」 「まとめて取得できないか」 こうした意識を持つだけで、 アプリの動作は大きく変わります。
初心者のうちは、includesとBulletを味方につけて、 安心してRailsのモデル設計を進めていきましょう。