March 17, 2025
DataLoaderは、GraphQLアプリケーションでデータベースクエリを効率的にバッチ処理およびキャッシュするためにFacebookが開発した汎用ユーティリティです。重複するクエリを減らし、次のような問題を解決するのに役立ちます。 N+1 クエリ問題 複数のクエリを 1 つのバッチリクエストにまとめます。
DataLoader を GraphQL リゾルバーに統合することで、API の効率とスケーラビリティを大幅に向上させることができます。
GraphQL API は柔軟にデータを取得できるため、クライアントは必要なデータのみをリクエストできます。ただし、この柔軟性により、 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 リゾルバーは関連データを一括で効率的に取得できます。📦🔗⚡
まだインストールされていない場合は、DataLoader をプロジェクトに追加します。
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;
});
個別のクエリを作成する代わりに 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
}
}
}
SELECT * FROM users;
SELECT * FROM posts WHERE userId = 1;
SELECT * FROM posts WHERE userId = 2;
...
SELECT * FROM posts WHERE userId = 10;
クエリの総数: 11
SELECT * FROM posts WHERE userId IN (1,2,3,4,5,6,7,8,9,10);
クエリの総数: 2
クエリの数を 11 から 2 に減らすことで、DataLoader はデータベースの負荷を大幅に軽減し、応答時間を改善します。データセットが大きくなるほどクエリの減少が顕著になり、スケーラビリティとパフォーマンス効率が向上します。DataLoader が登場する前は、10 人のユーザーの投稿を取得すると次のような結果になっていました。 11 クエリ。データローダーを使用すると、次のようになります。 2 クエリ:
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;
});
const userPostLoader = new DataLoader(async (userIds) => {
const posts = await Post.findAll({
where: { userId: userIds },
});
return userIds.map(id => posts.filter(post => post.userId === id));
});
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()));
});
GraphQL APIでDataLoaderを使用することは、データベースクエリを最適化し、N+1クエリ問題を解決するために不可欠です。リクエストをバッチ処理して結果をキャッシュすることで、パフォーマンスとスケーラビリティが大幅に向上します。Prisma、Sequelize、MongoDB のいずれを使用していても、DataLoader を統合することは GraphQL API を効率化するためのベストプラクティスです。