N+1 (1+N) 問題について

N+1 問題とは ?
N+1問題は、主にデータベースのクエリを扱う際に出くわす可能性のあるパフォーマンス上の問題で、特にオブジェクト関係マッピング (ORM) ツールを使用しているときによく見られます。
この問題は、リレーションシップが存在する2つのデータセット(例えば、親と子の関係にあるデータ)を取得する際に発生します。親データを1つ取得し、その後、その親に関連する各子データを個別に取得するという方法でデータを取得すると、データベースに対するクエリ数が増え、それによってパフォーマンスが低下する可能性があります。
https://teteteo619.com/blog/n+1
- N+1 の「1」: 親テーブルのデータを取得するためのクエリを「1 回」の実行回数で取得
- N+1 の「N」: 子テーブルのデータのうち、↑と紐づくデータを取得するためのクエリを「N 回」の実行回数で取得
要点
N 回の無駄なクエリ実行をせず 1 回で済ませることにより、 問い合わせの回数を最小に抑える こと

Batching or Bulk Fetching
バッチ処理やバルクフェッチは、必要なデータを一度に取得することでクエリの数を減らします。例えば、特定のユーザーのリストを持っていて、それぞれのユーザーに対応する情報をデータベースから取得する場合、各ユーザーに対して個別にクエリを実行するのではなく、一度のクエリで全てのユーザー情報を取得します。SQLのIN句を使って、一つのクエリで複数のIDにマッチするレコードを取得することができます。
https://teteteo619.com/blog/n+1#:~:text=Batching or Bulk Fetching%3A
SELECT * FROM users WHERE id = 1;
SELECT * FROM users WHERE id = 2;
SELECT * FROM users WHERE id = 3;
...
SELECT * FROM users WHERE id = N;
↓
SELECT * FROM users WHERE id IN (1, 2, 3, ..., N);
- 個別の条件を IN 句に指定する

Data Loader Pattern
これは主にGraphQLなどのAPIで使われる手法です。リクエストが来た時にすぐにデータを取得するのではなく、一定時間待ってから、その間に集まった同じ種類のクエリを一つのクエリにまとめて実行します。例えば、あるリクエストでユーザーAの情報を取得し、すぐに別のリクエストでユーザーBの情報を取得する場合、2つのクエリを一つにまとめて、ユーザーAとBの情報を一度に取得します。
https://teteteo619.com/blog/n+1#:~:text=Data Loader Pattern%3A
-
Bulk Fetching
の WHERE IN の条件の id を、複数のリクエストまとめて指定し、実行するもの - GraphQLの Query では、取得したいデータのノードを辿って必要なデータを一度に取得できるが、ノードの深さの分だけ Query を実行することによるパフォーマンスの懸念がある
- という背景により、GraphQL では「リクエストがあった id をためておいて、まとめて実行することで N+1 を回避する方法をとる」と思っておく

Eager Loading
// 1 回クエリを実行
$posts = App\Post::all();
// N 回 ($postsの数分) クエリを実行
foreach ($posts as $post) {
$tags = App\Tags::where('post_id', $post->id)->get();
}
↓
$posts = App\Post::with('tags')->get();
- JOIN を使って関連する情報を取得する