Blogs

DataLoader を使用して GraphQL でデータベースクエリをバッチ処理および最適化する

March 17, 2025

データローダーとは

DataLoaderは、GraphQLアプリケーションでデータベースクエリを効率的にバッチ処理およびキャッシュするためにFacebookが開発した汎用ユーティリティです。重複するクエリを減らし、次のような問題を解決するのに役立ちます。 N+1 クエリ問題 複数のクエリを 1 つのバッチリクエストにまとめます。

データローダーの主な機能:

  1. バッチ処理: 複数のデータベースリクエストを 1 つのクエリにまとめてパフォーマンスを最適化します。
  2. キャッシュ: 同じリクエストサイクルでデータベース呼び出しが重複しないように結果を保存します。
  3. 非同期実行: Promise を使用して複数のデータベースリクエストを効率的に処理します。

DataLoader を GraphQL リゾルバーに統合することで、API の効率とスケーラビリティを大幅に向上させることができます。

データローダーが必要な理由 🧐

GraphQL API は柔軟にデータを取得できるため、クライアントは必要なデータのみをリクエストできます。ただし、この柔軟性により、 N+1 クエリ問題は、関連データへの複数のクエリがデータベースの非効率性を引き起こす一般的なパフォーマンス上の問題です。

N+1 クエリの問題 🏗️

GraphQL リゾルバーでは、関連データを取得するために親レコードごとに個別のクエリが必要になると、データベース呼び出しが指数関数的に増加します。例を考えてみましょう。

const resolvers = {
  Query: {
    users: async () => await db.User.findAll(),
  },
  User: {
    posts: async (parent) => await db.Post.findAll({ where: { userId: parent.id } })
  }
};

10 人のユーザーとその投稿を取得すると、リゾルバーは最初にユーザーを検索します (1 クエリ)、次に、各ユーザーの投稿を取得します (10 件の追加クエリ)。この結果、 11 クエリ 最適なものではなく 2 クエリ

データローダーがこの問題を解決する方法

データローダー 複数のクエリを 1 つのリクエストにまとめ、結果をキャッシュして呼び出しが重複しないようにします。これにより、GraphQL リゾルバーは関連データを一括で効率的に取得できます。📦🔗⚡

段階的な実装 📝

1。データローダー 🔧 をインストールします。

まだインストールされていない場合は、DataLoader をプロジェクトに追加します。

2。クエリをバッチ処理するためのデータローダーの作成 📊

GraphQL セットアップで、データベースクエリをバッチ処理してキャッシュする DataLoader インスタンスを定義します。

const DataLoader = require('dataloader');
const db = require('./models');

const userPostLoader = new DataLoader(async (userIds) => {
  const posts = await db.Post.findAll({ where: { userId: userIds } });

  const postsByUserId = userIds.map(id => posts.filter(post => post.userId === id));
  return postsByUserId;
});

3。データローダーをリゾルバーに統合 🔌

個別のクエリを作成する代わりに DataLoader を使用するようにリゾルバーを変更します。

const resolvers = {
  Query: {
    users: async () => await db.User.findAll(),
  },
  User: {
    posts: (parent, args, context) => context.userPostLoader.load(parent.id)
  }
};

データローダーをコンテキストにアタッチ 🔗

DataLoader を GraphQL サーバー設定のコンテキストに追加して、各リクエストで利用可能であることを確認します。

const { ApolloServer } = require('apollo-server');

const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: () => ({
    userPostLoader: userPostLoader
  })
});

パフォーマンス向上の検証 📊

DataLoader を実装する前に、実行されているクエリの数を分析しましょう。次の GraphQL クエリを考えてみましょう。

query {
  users {
    id
    name
    posts {
      id
      title
    }
  }
}

データローダーなし:❌

  1. ユーザーを取得するクエリ (1 クエリ)
 SELECT * FROM users;

  1. 各ユーザーの投稿のクエリ (10 人のユーザーがいる場合は 10 クエリ)
 SELECT * FROM posts WHERE userId = 1;
 SELECT * FROM posts WHERE userId = 2;
 ...
 SELECT * FROM posts WHERE userId = 10;

クエリの総数: 11

データローダー付き:✅

  1. ユーザーを取得するクエリ (1 クエリ)

  1. すべての投稿を一度に取得する単一バッチクエリ (1 クエリ)
 SELECT * FROM posts WHERE userId IN (1,2,3,4,5,6,7,8,9,10);

クエリの総数: 2

パフォーマンスの向上:🚀

クエリの数を 11 から 2 に減らすことで、DataLoader はデータベースの負荷を大幅に軽減し、応答時間を改善します。データセットが大きくなるほどクエリの減少が顕著になり、スケーラビリティとパフォーマンス効率が向上します。DataLoader が登場する前は、10 人のユーザーの投稿を取得すると次のような結果になっていました。 11 クエリ。データローダーを使用すると、次のようになります。 2 クエリ:

  1. すべてのユーザーを取得
  2. 1 つのバッチクエリですべての投稿を取得

データローダーとプリズマ/シークライズ/MongoDB 🛠️ の統合

Prisma でのデータローダーの使用

const userPostLoader = new DataLoader(async (userIds) => {
  const posts = await prisma.post.findMany({
    where: { userId: { in: userIds } },
  });

  const postsByUserId = userIds.map(id => posts.filter(post => post.userId === id));
  return postsByUserId;
});

Sequelizeでのデータローダーの使用

const userPostLoader = new DataLoader(async (userIds) => {
  const posts = await Post.findAll({
    where: { userId: userIds },
  });

  return userIds.map(id => posts.filter(post => post.userId === id));
});

MongoDB (マングース) でのデータローダーの使用

const userPostLoader = new DataLoader(async (userIds) => {
  const posts = await Post.find({ userId: { $in: userIds } });

  return userIds.map(id => posts.filter(post => post.userId.toString() === id.toString()));
});

データローダーの使用による影響

  1. パフォーマンスの向上: データベースクエリを減らし、応答時間を大幅に最適化します。
  2. スケーラビリティの向上: 大規模な GraphQL クエリを効率的に処理し、API をよりスケーラブルにします。
  3. データベース負荷の軽減: バッチ処理によりクエリの数が減り、データベースへの負荷が最小限に抑えられます。
  4. キャッシュのメリット: 以前に取得したデータをキャッシュすることで、データベース呼び出しの重複を回避します。

結論 💡

GraphQL APIでDataLoaderを使用することは、データベースクエリを最適化し、N+1クエリ問題を解決するために不可欠です。リクエストをバッチ処理して結果をキャッシュすることで、パフォーマンスとスケーラビリティが大幅に向上します。Prisma、Sequelize、MongoDB のいずれを使用していても、DataLoader を統合することは GraphQL API を効率化するためのベストプラクティスです。