1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Basic Study LogAdvent Calendar 2024

Day 23

GraphQLを1から勉強しなおしてみた ~ DataLoader ~

Last updated at Posted at 2024-12-22

はじめに

先日に引き続きGraphQLを一から勉強し直していきます。

前回はこちら。

N+1問題

DataLoaderについて見ていく前に、N+1問題について再度確認していく。

N+1問題とは、データベースからデータを取得する際に、1つのメインクエリに加えて、関連するデータを取得するためにN個の追加クエリが発行される問題である。これにより、パフォーマンスが大幅に低下してしまう。

例として、ユーザーとユーザーに紐づく投稿を取得する場合を考える。
最初に全ユーザーを取得し(1つのクエリ)、その後各ユーザーの投稿を取得するためにユーザーごとクエリが発行されるため(N人のユーザーがいるとして、N個のクエリ)、全部でN+1個のクエリが発行される。

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

DataLoader

N+1問題を解決する方法がDataLoaderである。

DataLoaderを使うことで、以下の点からバックエンドへのリクエストを減らし、パフォーマンスを向上してくれる。

  • バッチ処理: 複数のデータ取得リクエストを1つのバッチにまとめて処理することで、バックエンドへのリクエスト数を減らす
  • キャッシング: 同じデータに対する複数のリクエストをキャッシュすることで、重複するデータ取得を避ける

以下はGraphQL、Nest.js、prismaを使ってDataLoaderを実装した例である。
(DataLoaderに大きく関係してくる箇所以外は省略している)

prisma
model User {
  id    Int     @id @default(autoincrement())
  name  String
  posts Post[]
}

model Post {
  id     Int    @id @default(autoincrement())
  title  String
  userId Int
  user   User   @relation(fields: [userId], references: [id])
}
graphql
type User {
  id: ID!
  name: String!
  posts: [Post!]!
}

type Post {
  id: ID!
  title: String!
}

type Query {
  users: [User!]!
}
Resolver
import { Resolver, Query, ResolveField, Parent } from '@nestjs/graphql';
import { UsersService } from './users.service';
import * as DataLoader from 'dataloader';

@Resolver('User')
export class UsersResolver {
  private postLoader: DataLoader<number, any[]>;

  constructor(private readonly service: UsersService) {
    // DataLoaderから内部のキューに溜まった全てのキーを渡されて処理をする
    this.postLoader = new DataLoader(async (userIds: number[]) => {
      const posts = await this.service.findPostsByUserIds(userIds);
      return posts;
    });
  }

  @Query('users')
  async getUsers() {
    return this.service.findAllUsers();
  }

  @ResolveField('posts')
  async getPosts(@Parent() user) {
    const { id } = user;
    return this.postLoader.load(id); // キー(id)を内部にキューイング
  }
}
Service
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma.service';

@Injectable()
export class UsersService {
  constructor(private prisma: PrismaService) {}

  async findAllUsers() {
    return this.prisma.user.findMany();
  }

  async findPostsByUserIds(userIds: number[]) {
    const posts = await this.prisma.post.findMany({
      where: { userId: { in: userIds } },
    });
    return userIds.map(userId => posts.filter(post => post.userId === userId));
  }
}
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?