改善・モダナイゼーション

N+1問題を見つけて既存画面を高速化する方法

2026年07月10日
パフォーマンス Rails N+1問題 既存改善 高速化

「サービス開始当初はサクサク動いていたのに、データが増えるにつれて一覧画面がどんどん重くなってきた」——これは私たちがパフォーマンス改善の相談を受けるときに、最も高い頻度で聞くお悩みです。特に注文一覧、ユーザー一覧、記事一覧のような「何かのリストを表示する画面」で顕著に起きます。

原因を調べると、8割以上のケースでN+1問題が見つかります。N+1問題は名前が分かりにくいので敬遠されがちですが、仕組みは実はシンプルです。この記事では、発注者の方にも「なぜ遅いのか」が伝わるように噛み砕いて説明したうえで、検出方法と直し方、そして直すときの注意点を整理します。

N+1問題とは何か

一覧画面を作るとき、開発者はよく「まず一覧を取得して、それぞれの行に関連する情報を表示する」という作りにします。例えば「注文一覧に、注文した顧客の名前を添えて表示する」場面を考えてみましょう。

このとき、内部では次のようなデータベースへの問い合わせが起きることがあります。

  1. 「注文を全部ください」という問い合わせを1回
  2. 表示する注文が100件あれば、「この注文をした顧客は誰ですか」という問い合わせを注文の数だけ100回

合計101回の問い合わせが発生します。これが「N+1」という名前の由来です(1件の一覧取得+N件の関連データ取得)。注文が100件なら101回、1000件なら1001回。表示するデータが増えるほど、問い合わせの回数が比例して増えるため、「最初は快適だったのにデータが増えると重くなる」という症状にぴったり一致します。

データベースへの1回の問い合わせには、通信のオーバーヘッドや処理のコストがかかります。1回なら数ミリ秒でも、それが100回・1000回と積み重なると、画面の表示に数秒かかる、あるいはタイムアウトするという事態につながります。ユーザーからは「このサイト、重い」としか見えませんが、実態は「同じような小さな問い合わせを大量に繰り返している」だけ、というケースが非常に多いのです。

なぜ気づかれずに残ってしまうのか

N+1問題が厄介なのは、開発時やテスト環境では気づきにくいという点です。テストデータが数件〜数十件しかない状態では、101回の問い合わせも一瞬で終わるため、体感速度に差が出ません。ところが本番でデータが数千件・数万件に増えると、同じコードが致命的な遅さを生み出します。

つまりN+1問題は「書いた瞬間のバグ」ではなく、「データの成長とともに顕在化する時限爆弾」に近い性質を持っています。リリース当初は問題なかった画面が、事業が順調に伸びてデータが増えるほど遅くなっていく——という皮肉な構図は、こうした事情から生まれます。

どうやって見つけるか

N+1問題を見つけるには、実際に発生しているデータベースへの問い合わせを観察するのが最も確実です。

  • 開発ログを見る: Railsの開発環境ログには、実行されたSQLが逐一出力されます。1つの画面を開いただけで同じ形のSQLが何十行も繰り返し出ていたら、それがN+1のサインです。
  • bullet gemのような検出ツールを使う: Railsには bullet という、N+1問題を自動検出してブラウザや画面上に警告を出してくれるgemがあります。開発中に気づかず作り込んでしまったN+1を、後から機械的に洗い出すのに向いています。
  • 本番相当のデータ量で計測する: 開発ログやbulletは開発環境で有効ですが、本番と同じ規模のデータでないと再現しない・気づかないケースもあります。ステージング環境に本番相当のデータ件数を用意し、実際の応答時間を測ることが重要です。

私たちが既存プロダクトの改善に入るときも、まずこの「今どこで何回問い合わせが起きているか」を計測するところから着手します。感覚や勘で「ここが怪しい」と決め打ちして直し始めると、実際のボトルネックを外してしまうことが少なくありません。

直し方: eager loading(includes/preload)

N+1問題の基本的な解決策は、関連データを最初にまとめて取得しておくことです。Railsではこれを「eager loading(先読み)」と呼び、includespreload といった仕組みを使います。

先ほどの注文一覧の例で言えば、「注文を100件取得する」問い合わせと「関連する顧客をまとめて取得する」問い合わせの、合計2回で済むように書き換えます。101回だった問い合わせが2回になるわけですから、表示速度の改善効果は非常に大きく、体感で数秒かかっていた画面が一瞬で表示されるようになることも珍しくありません。

includes は関連の取得方法をRails側が状況に応じて自動選択する汎用的な方法で、preload は「必ず別々の問い合わせで先読みする」という明示的な指定です。表示条件によってはさらに細かい調整(eager_load でJOINして絞り込みも同時に行うなど)が必要な場合もありますが、発注者の方に押さえていただきたいのは「関連データをその都度取りに行くのではなく、まとめて先に取っておく」という考え方そのものです。

闇雲にincludesしない注意

ここで一つ、技術的に見落とされがちな注意点があります。「とりあえず全部 includes しておけば安全」ではないということです。

  • 使われない関連データまで先読みすると、かえって無駄なメモリと処理時間を消費し、画面によっては逆に遅くなることがあります
  • 条件によって表示する関連データが変わる画面に、一律で同じ includes を適用すると、不要な問い合わせが増える場合があります
  • 大量の関連データを一度にまとめて取得すると、1回あたりの問い合わせが重くなりすぎることもあります

つまりN+1の解決は「includesを付ければ終わり」という単純作業ではなく、その画面が実際に何を表示していて、どのデータが本当に必要かを理解した上で、必要な範囲だけ先読みする設計判断が伴います。だからこそ、直す前の「計測してどこが遅いかを特定する」工程が欠かせません。計測を飛ばして機械的にincludesを足していくと、コードは複雑になるのに体感速度は改善しない、という徒労に終わりがちです。

一覧画面以外にも起きる

N+1問題は一覧画面が代表例ですが、管理画面のダッシュボード、APIのレスポンス生成、バッチ処理での大量データ集計など、「複数件のデータをループしながら関連情報を参照する」あらゆる場所で起こり得ます。管理画面が重いという相談の裏側にも、同じ原因が潜んでいることがよくあります。

torcheees ではこうしたパフォーマンス改善を、モダナイゼーション支援の一環として、Ruby on Railsを中心に数多く手がけています。改善の進め方の全体像は既存プロダクト改善で外部チームが最初に見る観点で、アプリ全体の速度に課題がある場合の診断アプローチは遅いRailsアプリのパフォーマンス診断で詳しく解説しています。

まとめ

  • N+1問題は「一覧の取得1回+関連データの取得N回」という構造上、データが増えるほど比例して問い合わせが増え、体感速度を悪化させる
  • ログ観察やbullet gemなどのツールで検出し、includes / preload によるeager loadingで問い合わせ回数をまとめて削減できる
  • ただし闇雲にincludesを足すのは逆効果になり得るため、「何が本当に必要か」を計測した上で直すことが重要

「データが増えるほど画面が重くなっている」という症状に心当たりがあれば、まず現状の診断から始めることをおすすめします。torcheees では既存プロダクト改善の診断と、継続的な改善支援を提供しています。まずはお問い合わせフォームから現状を聞かせてください。

プロダクト開発の相談をする

開発相談をする
簡易見積もり