ふくふくHukuhuku Inc.
EP.08Incident 10分公開: 2026-05-10

N+1問題:ORM・GraphQL の典型病、なぜ100倍遅くなるのか

ORMやGraphQLでサクッと書いたら、本番で100倍のクエリが発行されていた。SELECT N+1問題の正体・検知法・解消パターン。Rails / Django / Laravel / TypeORM 共通。

#N+1#ORM#Rails#Django#GraphQL#パフォーマンス
シェア

ローカルで開発した「ユーザ一覧画面」、本番で30秒かかる。理由は N+1問題:ユーザ100人を取るのに `SELECT user`(1回)+ `SELECT addresses WHERE user_id=?`(100回)= 101回のSQL が飛んでる。

あの時こうすれば良かった、と思う症状

・ローカルで快速・本番で激重 / ・APM のトレースが赤い線で埋まる / ・「100人ユーザ」と「1万人ユーザ」で表示時間が線形ではなく爆発 / ・GraphQL のネスト解決で謎の遅延

起きる仕組み

ORM が「親レコードのループ中に、子のリレーションをアクセスする」と、各 iteration で が飛びます。書き手は意識せず、`user.address.city` のような自然な書き方で遅延発火。

Rails の典型的 N+1(コードは綺麗に見えるのに)
Ruby
# 100ユーザの住所都市を表示する@users = User.where(active: true).limit(100)
@users.each do |u|  puts u.address.city  # ← user 100 人 × address 取得 100 回end# 結果:SELECT users + 100回の SELECT addresses
解消:includes / preload / eager_load
Ruby
# 1回の追加 SQL で全 address を先取り@users = User.where(active: true).limit(100).includes(:address)
@users.each do |u|  puts u.address.cityend# 結果:SELECT users + 1回の SELECT addresses WHERE user_id IN (...)

調査手順

  • ローカルログで `BEGIN` 〜 `COMMIT` 内のクエリ数:1リクエスト10個超は黄信号、100個超は赤信号
  • bullet (Rails) / nplusone (Django) の検出 gem:開発環境で N+1 を自動検出して例外
  • APM (Datadog / New Relic) のトレース:本番で実際の SQL 発行頻度を可視化
  • APM の slow trace:100ms 以上のリクエストのうち、SQL 数 vs 時間で比例しているもの
  • GraphQL の場合:DataLoader が入ってないリゾルバの個別アクセス数

解消パターン(フレームワーク別)

フレームワークN+1解消の標準手段
Rails (ActiveRecord)`.includes(:assoc)` / `.preload` / `.eager_load`
Django (ORM)`prefetch_related()` / `select_related()`
Laravel (Eloquent)`with()` / `load()`
TypeORM / Prisma`relations` オプション / `include`
GraphQLDataLoader(バッチ + キャッシュ) / `@nestjs/graphql` の `Loader`
SQLAlchemy`joinedload()` / `selectinload()`
.NET EF Core`.Include()`

中長期対策

  • に N+1 検知:bullet / nplusone を CI 環境で fail させる
  • APM で監視:本番で SQL 数の異常をアラート化
  • コードレビューの観点に追加:「ループの中でリレーション辿ってないか」
  • メトリクス:`SQL queries per request` を SLI として p95 を見る
  • GraphQL は DataLoader 必須:「リゾルバ書いたら DataLoader」のセット文化

ふくふくの進め方

N+1 監査は1〜2週間:① APM のトレースから「クエリ数 > 50」のリクエストを抽出、② 該当箇所をリスト化、③ includes / DataLoader 化の PR を一気に。Rails / Django / Laravel / GraphQL いずれも対応経験あります。

シェア

この記事の感想を教えてください

あなたの 1 クリックで、本当にこの記事は更新されます。「もっと詳しく」「続編希望」が一定数集まった記事は、 ふくふくが 実際に内容を拡充したり続編記事を公開 します。 送信したリアクションはお使いのブラウザに記録され、再カウントされません。

シリーズの外も探す:

まずは、現状を聞かせてください。

要件が固まっていなくて大丈夫です。現状診断と方針提案までを無料でお手伝いします。

無料相談フォームへ hello [at] hukuhuku [dot] co [dot] jp