NestJS graphql (4) dataloader

peter0618·2020년 10월 17일
1

nestjs

목록 보기
7/9
post-thumbnail

1.dataloader 도입 전

post.service.ts (AS-IS)

import { Injectable } from '@nestjs/common';

@Injectable()
export class PostService {

    private posts = [
        {
            id: 1,
            userId: 1,
            title: '테스트 글제목1',
            votes: 1,
        },
        {
            id: 2,
            userId: 2,
            title: '테스트 글제목2',
            votes: 2,
        },
        {
            id: 3,
            userId: 2,
            title: '테스트 글제목3',
            votes: 3,
        },
        {
            id: 4,
            userId: 4,
            title: '테스트 글제목4',
            votes: 4,
        },
        {
            id: 5,
            userId: 4,
            title: '테스트 글제목5',
            votes: 5,
        },
        {
            id: 6,
            userId: 4,
            title: '테스트 글제목6',
            votes: 6,
        }
    ];

    async findAllByUserId(id: number) {
        return this.posts.filter(post => post.userId === id);
    }
}

graphql 쿼리

query{
  users{
    id
    name
    age
    posts{
      id
      userId
      title
      votes
    }
  }
}

"NestJS graphql (3) 간단한 1:N 관계 예제" 글에서 잠깐 언급했던 1:N 문제를 해결하는 방법 중의 하나로 dataloader를 도입해봅시다.
dataloader 도입 전, 위 graphql 쿼리를 실행하면 findAllByUserId 함수가 User의 개수 만큼 실행이 됩니다.

2.dataloader 도입 후

post.service.ts (TO-BE)

import { Injectable } from '@nestjs/common';
import { Post } from '../../autogen/schema.graphql';
import * as DataLoader from 'dataloader';

@Injectable()
export class PostService {
  private dataLoaderPost;

  constructor() {
    this.dataLoaderPost = new DataLoader<number, Post[]>(
      (userIds: readonly number[]) => {
        console.log(userIds);
        return this.getPostsByIds(userIds);
      },
      { cache: false },
    );
  }

  private posts: Post[] = [
    {
      id: 1,
      userId: 1,
      title: '테스트 글제목1',
      votes: 1,
    },
    {
      id: 2,
      userId: 2,
      title: '테스트 글제목2',
      votes: 2,
    },
    {
      id: 3,
      userId: 2,
      title: '테스트 글제목3',
      votes: 3,
    },
    {
      id: 4,
      userId: 4,
      title: '테스트 글제목4',
      votes: 4,
    },
    {
      id: 5,
      userId: 4,
      title: '테스트 글제목5',
      votes: 5,
    },
    {
      id: 6,
      userId: 4,
      title: '테스트 글제목6',
      votes: 6,
    },
  ];

  async findAllByUserId(id: number): Promise<Post[]> {
    return this.dataLoaderPost.load(id);
  }

  async getPostsByIds(userIds: readonly number[]): Promise<Post[][]> {
    console.log(`getPostsByIds(k: ${userIds})`);
    const rs = userIds.map(userId => this.posts.filter(post => post.userId === userId));
    console.log(rs);
    return rs;
  }
}

findAllByUserId 함수에서 직접 db에 접근했던 로직을 dataLoaderPost.load()를 호출하도록 변경했습니다.
여전히 findAllByUserId 함수는 N번 실행(dataLoaderPost.load도 N번 실행됩니다) 되겠지만, db에 접근하는 로직(getPostsByIds 함수)은 한번만 실행됩니다.
(getPostsByIds 함수가 로직을 처리하는 시간이 오래 걸리면 여러번 실행될 수도 있습니다. dataloader의 내부 원리는 자바스크립트에서 이벤트 루프가 돌아가는 한 사이클동안 들어온 id 기반 요청을 모아 배치로 처리한 후 값을 되돌려주는 방식이기 때문입니다.)

이런식으로 dataloader를 도입하면 1:N 문제에서 N번 db에 접속하게 되는 상황을 해결할 수 있습니다.

3.ref

https://y0c.github.io/2019/11/24/graphql-query-optimize-with-dataloader/

0개의 댓글