title | emoji | type | topics | published | ||
---|---|---|---|---|---|---|
PrismaのFluentAPIでN+1問題に対応する |
😊 |
tech |
|
true |
PrismaでGraphQL APIなどの開発をしていると、N+1問題に遭遇することがあると思います. 今回は、PrismaのFluentAPIを使ってN+1問題に対応する方法を紹介します.
N+1問題やPrismaについては本記事では省略します
https://zenn.dev/nyatinte/articles/656822f279cb37
上記の記事で作成したサンプルリポジトリを使います
https://github.com/nyatinte/bun-yoga-server-preset-test
お手元の環境でgit clone
してREADMEに従ってセットアップを行えばOKです
実際にサーバーを起動して問題を再現してみます
bun start
でサーバーを起動したら、簡単なクエリを投げてみます
{
users {
id
posts {
id
}
}
}
ターミナルを見てみるとQueryのログが出力されているのがわかります
prisma:query SELECT `main`.`User`.`id`, `main`.`User`.`firstName`, `main`.`User`.`lastName`, `main`.`User`.`createdAt`, `main`.`User`.`updatedAt` FROM `main`.`User` WHERE 1=1 LIMIT ? OFFSET ?
prisma:query SELECT `main`.`Post`.`id`, `main`.`Post`.`title`, `main`.`Post`.`content`, `main`.`Post`.`published`, `main`.`Post`.`authorId`, `main`.`Post`.`createdAt`, `main`.`Post`.`updatedAt` FROM `main`.`Post` WHERE `main`.`Post`.`authorId` IN (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) LIMIT ? OFFSET ?
prisma:query SELECT `main`.`Post`.`id`, `main`.`Post`.`title`, `main`.`Post`.`content`, `main`.`Post`.`published`, `main`.`Post`.`authorId`, `main`.`Post`.`createdAt`, `main`.`Post`.`updatedAt` FROM `main`.`Post` WHERE `main`.`Post`.`authorId` = ? LIMIT ? OFFSET ?
prisma:query SELECT `main`.`Post`.`id`, `main`.`Post`.`title`, `main`.`Post`.`content`, `main`.`Post`.`published`, `main`.`Post`.`authorId`, `main`.`Post`.`createdAt`, `main`.`Post`.`updatedAt` FROM `main`.`Post` WHERE `main`.`Post`.`authorId` = ? LIMIT ? OFFSET ?
...
prisma:query SELECT `main`.`Post`.`id`, `main`.`Post`.`title`, `main`.`Post`.`content`, `main`.`Post`.`published`, `main`.`Post`.`authorId`, `main`.`Post`.`createdAt`, `main`.`Post`.`updatedAt` FROM `main`.`Post` WHERE `main`.`Post`.`authorId` = ? LIMIT ? OFFSET ?
prisma:query SELECT `main`.`Post`.`id`, `main`.`Post`.`title`, `main`.`Post`.`content`, `main`.`Post`.`published`, `main`.`Post`.`authorId`, `main`.`Post`.`createdAt`, `main`.`Post`.`updatedAt` FROM `main`.`Post` WHERE `main`.`Post`.`authorId` = ? LIMIT ? OFFSET ?
大量のクエリが発行されていることがわかります これをFluentAPIを使用して解決していきます
https://www.prisma.io/docs/concepts/components/prisma-client/relation-queries#fluent-api
FluentAPI(Fluent Interface)とは、オブジェクト指向APIの実装の一種です。 このAPIは、オブジェクト自体を返すメソッドを使用して、複数のメソッド呼び出しを一つの式に連鎖させることができます。
Prismaでは、FluentAPIを用いてリレーションのあるクエリを記述することができます。例えば、以下のようにfindUnique
と.posts()
をチェーンさせることで、あるUser
のすべてのPost
を取得できます。
const posts = await prisma.user.findUnique({ where: { id: parent.id } }).posts();
実装をFluentAPIに変更します
import type { UserResolvers } from './../../types.generated';
export const User: UserResolvers = {
fullName: (parent) => `${parent.firstName} ${parent.lastName}`,
posts: (parent, _, { prisma }) => {
/** using Fluent API */
+ return prisma.user.findUnique({ where: { id: parent.id } }).posts();
/** not using Fluent API */
- return prisma.post.findMany({ where: { authorId: parent.id } });
},
};
もう一度クエリを投げてみます
prisma:query SELECT `main`.`User`.`id`, `main`.`User`.`firstName`, `main`.`User`.`lastName`, `main`.`User`.`createdAt`, `main`.`User`.`updatedAt` FROM `main`.`User` WHERE 1=1 LIMIT ? OFFSET ?
prisma:query SELECT `main`.`Post`.`id`, `main`.`Post`.`title`, `main`.`Post`.`content`, `main`.`Post`.`published`, `main`.`Post`.`authorId`, `main`.`Post`.`createdAt`, `main`.`Post`.`updatedAt` FROM `main`.`Post` WHERE `main`.`Post`.`authorId` IN (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) LIMIT ? OFFSET ?
prisma:query SELECT `main`.`User`.`id` FROM `main`.`User` WHERE `main`.`User`.`id` IN (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) LIMIT ? OFFSET ?
prisma:query SELECT `main`.`Post`.`id`, `main`.`Post`.`title`, `main`.`Post`.`content`, `main`.`Post`.`published`, `main`.`Post`.`authorId`, `main`.`Post`.`createdAt`, `main`.`Post`.`updatedAt` FROM `main`.`Post` WHERE `main`.`Post`.`authorId` IN (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) LIMIT ? OFFSET ?
発行されるクエリが大きく減っていることがわかります
https://www.prisma.io/docs/concepts/components/prisma-client/relation-queries#fluent-api
簡単に説明すると、PrismaのDataLoaderが同じパラメータと 選択セット(includeなど) を持つクエリをグループ化し、バッチ処理を行うようになっているからです. 詳細は上記の公式ドキュメントを参照してください
この記事では、PrismaのFluentAPIを使用してN+1問題に対処する方法を解説しました. 等価な処理でも書き方1つで大きくパフォーマンスが変わるので、ぜひ使ってみてください!