Prisma로 Pagination 구현하기

김민국·2021년 8월 29일
2

Prisma

목록 보기
1/1
post-thumbnail

Pagination

우리가 인스타그램이나 페이스북과 같은 sns를 사용할 때 피드를 볼 수 있다.
우리는 피드는 스크롤을 내리면서 보게 되는데 어느정도 내리면 추가로 로딩해서 더 많은 피드를 보여주고 더 필요하면 또 새로운 피드를 로딩해서 보여준다.

만약에 한 번에 내가 볼 수 있는 피드를 모~~두 한번에 로딩한다면 데이터베이스에도 무리가 가고 사용자는 엄청난 로딩을 기다려야 할 것이다.

위와 같은 이유 때문에 조금씩 페이지로 나누어 로딩해주는 것을 Pagination이라고 한다.

Prisma에서 pagination을 구현하는 것은 아주 간단한데,
offset pagination과 cursor-based pagination에 대해 알아보자.

skip / take

pagination을 구현하는 법을 알기 이전에 prisma의 skip, take에 대해 먼저 알아야 한다. 밑의 코드를 보자.

const results = await prisma.post.findMany({
  skip: 3,
  take: 4,
});

코드의 진행은 다음과 같다.

1. findMany를 사용해 post들을 불러온다.
2. 앞에서부터 3가지 post를 skip한다.
3. skip된 post 직후의 4개의 post를 취한다(take).

다시 말하면
skip은 생략하는 data의 갯수이고, take는 가져올 data의 갯수이다.

원래 나와야 할 결과 값이 1~45까지 있다면 위의 예시에서는 맨 앞의 (1, 2, 5)가 skip 되고 결과값으로는 그 뒤의 4개의 데이터(9, 17, 19, 27)이 반환된다.

offset pagination

offset pagination은 데이터들을 page로 나누어 로딩하는 방법이다.

skip/take를 활용하면 페이지를 나누어 로딩해줄 수 있는데 코드를 보자.

const results = await prisma.post.findMany({
  skip: 5,
  take: 5,
});
const results = await prisma.post.findMany({
  skip: 10,
  take: 5,
});

다음과 같은 코드가 있다면
첫 코드는 5개를 skip하고 5개를 표시해주고,
두번째 코드는 10개를 skip하고 5개를 표시해준다.

어라? 뭔가 페이지가 넘어가는 느낌이 든다 ㅋㅋㅋ.
만약 내가 원하는 page를 input으로 받았다면

const results = await prisma.post.findMany({
  skip: (page - 1) * 5,
  take: 5,
});

이렇다면 page 1 일때는 skip하지 않고,
2일 때는 5개 skip
3일 때는 10개 skip ... 하면서 목표했던 페이지 분할 로딩 기능이 구현된다.

하지만... offset 방식은 단점이 있다.

바로 1,000,001번째 부터 5개를 표시하고 싶다면 1,000,000개의 데이터를 순환하며 skip해야 한다는 사실이다!

데이터의 양이 엄청나게 많아지고 스킵할게 엄청나게 많아진다면 성능이 점점 떨어질 수 있는 방식이다.

Cursor-based pagination

이 방법으로 pagination을 구현하면 offset 방식에서의 성능이슈를 겪지 않아도 된다.

offset pagination에서는 단순하게 skip, take만 지정해주면 됐었다.
여기서는 cursor라는 변수가 하나 더 추가된다.

설명하자면,

Offset pagination

"맨 앞에서부터 몇 개를 skip할 것인가"

에 대해서만 로직을 작성하여, page를 입력하면 그 page의 데이터를 반환했다.

Cursor-based pagination

"전에 봤던 페이지의 마지막 데이터가 무엇이었는지"
(그 데이터의 unique한 값)를 변수로 넘겨준다.

그렇게 받은 cursor에서 한 개를 skip하고 page를 표시해주는 방식이다.

// lastId는 input임
const results = await prisma.post.findMany({
        take: 4,
        skip: 1,
        cursor: { id: lastId }
});

위 처럼 코드를 작성하면 내가 input으로 넣어준 lastId를 id로 가지고 있는 데이터가 cursor가 된다.

그 데이터(29)를 기준으로 1개 skip 하고 그 뒤의 4개(32, 45, 49, 52)를 반환하는(take) cursor-based pagination이 완성된다.

하지만, 항상 skip을 해야할까?
위의 코드대로라면 전에 본 마지막 lastId가 없는 상황이어도 맨 처음 데이터가 skip이 되어버린다. 따라서 아래와 같은 짧은 코드를 추가해주면 된다.

const results = await prisma.post.findMany({
        take: 4,
        skip: lastId ? 1 : 0,
        ...(lastId && {cursor: { id: lastId }})
});

이러면 lastId의 존재 여부에 따라 skip을 하거나 안하도록 만들 수 있다.

...(lastPhotoId && {cursor: { id: lastPhotoId }}) 구문이 이해가 안된다면?


Prisma Doc

글은 Prisma 공식 문서를 바탕으로 작성되었습니다.

0개의 댓글