株式会社オートプロジェクト

GraphQLとN+1問題

GraphQLとN+1問題

N+1問題とは

N+1問題とは、データベースへのアクセスが、本来は1回(または少数回)で済むはずの処理にもかかわらず、取得したデータN件それぞれに対して追加の問い合わせを発行してしまうことで、アクセス数が件数に比例して線形増加する性能問題です。

特にORMを使用する際のアンチパターンという文脈で目にする方も多いと思います。

具体的には、まず一覧(複数件)を取得するためのアクセスが1回発生し、その後、各行の関連データ取得などのためにN回の追加アクセスが発生します。

まさに N+1回 のアクセスになるため、N+1問題と呼ばれます。

// 例: 顧客一覧を取得して、各顧客の担当者も取得する
// customers が N 件のとき、DBアクセスは 1 + N 回になる
​
const customers = await db.customer.findMany(); // 1回
​
for (const customer of customers) {
 // 各顧客ごとに担当者を取得している(N回)
 customer.staff = await db.staff.findUnique({
   where: { id: customer.staffId },
});
}

N+1問題を通してみる、REST APIとGraphQLの違い

REST API はエンドポイントごとにレスポンス形状が固定されるため、画面に必要な関連データ(例:顧客+担当者)をサーバー側でJOIN等によりまとめて取得し、少ないDBアクセスで返す設計にしやすいです。その結果、N+1が入り込みにくくなります。

一方、GraphQLは取得フィールドがリクエストごとに変わり、フィールド単位でリゾルバが実行されます。

たとえば、次のクエリは宣言的な見た目ですが、

{
  customers {
    id
    name
    staff { id name }
  }
}

実装次第で「customersは1回取得したが、staffは顧客件数だけ個別取得している」という状態になり得ます。リゾルバ内で関連取得を素朴に書くとN+1問題が発生しやすくなります。

どう防ぐ?

1. リレーションをまとめて取得する

基本は「行ごとの追加取得」をやめ、関連データを一括で取ります。ORMのeager loading(例:include)や、SQLのJOIN相当の発想で、1+N回を少数回に圧縮します。

2. DataLoaderパターンを併用する

GraphQLでは同じ型・同じフィールドの取得が散発的に繰り返されがちです。DataLoaderで取得要求をリクエスト内で集約し、まとめて問い合わせることで重複アクセスを減らします。

3. クエリ設計を見直す

深いネストを取りすぎない、1画面で大量の関連を取らない、ページングや条件指定を前提にする。GraphQLは見た目が短くても裏のDBアクセスが増えやすいため、どのフィールドが何回DBに行くかを意識して設計します。

実装テクニックだけでは不十分

GraphQLのN+1対策は、DataLoaderなどの実装テクニックだけで完結する話ではありません。GraphQLはクエリが柔軟であるぶん、どのフィールドをどれだけ取得するかによって、実行コストが簡単に増えます。

柔軟性とパフォーマンスはトレードオフになりやすいため、柔軟なツールを使うときほど、ボトルネック化しうる点がないか常に目を配る必要があります。

エンジニアのみなさまへ

株式会社オートプロジェクトでは、中小企業向けのシステム・アプリケーション開発 / 外注サービスを提供しております。

貴社のニーズに応じた柔軟なサポートを行いますので、ぜひお気軽にご相談ください。

中小企業向けシステム・アプリケーション開発 / 外注サービスについて、オートプロジェクトに問い合わせをする

Contact ご相談・お問い合わせ

実現の可否や概算費用、納期に関するご質問・ご相談も、
どうぞご遠慮なくお問い合わせください。

お問い合わせ ご相談・お問い合わせ
TOP