본문 바로가기
우아한테크코스 4기/레벨2

[SQL] 페이징 구현하기

by 나는후니 2022. 5. 7.

만약 수백만건의 데이터가 있는 상태로 아래 쿼리를 호출하면 어떨까요?

SELECT * FROM table;

데이터를 조회하는 시간이 굉장히 오래걸릴 뿐 아니라, 해당 데이터를 처리하는 것 또한 어려움을 느낄 것입니다.

 

이 문제를 해결하기 위해 데이터를 조회할 때 페이지에 따라 해당 페이지에 출력할 데이터만 조회하는 페이징을 사용합니다. 이번 포스팅에서는 가장 대중적인 방식의 페이징을 소개하고자 합니다.

Offset Pagination

offset Pagination은 offset과 limit를 지정하여 DB에서 출력하고자 하는 데이터 범위만큼의 데이터만 조회하는 방식입니다.

 

위 사진처럼 search.page=4를 지정해두고 해당 요청을 기반으로 offset을 설정해 페이지네이션을 할 수 있는 것이죠.

select * from crew order by id desc limit {size} offset {offset};

하지만 이 페이징 방식은 장점과 단점이 명확합니다.

장점

가장 큰 장점은 쉽게 사용할 수 있다는 것입니다. 넘어온 page * pageSize를 계산하여 offset을 만들어주면 DB에서 알아서 데이터를 조회해주기 때문입니다. 또한 전체 페이지를 입력해두고 사용자가 자유롭게 페이지를 이동하며 원하는 데이터를 조회할 수 있다는 것이 큰 장점입니다.

단점

하지만 큰 단점이 존재합니다. Sql 의 offset 키워드는 필요한 데이터를 찾을 때 까지 테이블을 full scan합니다. 즉, offset이 커질 수록 db에 발생하는 부하가 커지고, 성능 또한 저하될 가능성이 높습니다.

 

게다가 offset 기반으로 고정된 형태로 데이터를 조회하다 보니, 조회하는 시점에 데이터가 추가될 경우 중복된 데이터가 조회되는 것을 확인할 수 있습니다.

// 테스트 성공
@Test
void 중간에_데이터가_추가될_경우_중복데이터_노출() {
  for (int i = 1; i < 21; i++) {
    crewRepository.create(new Crew("name" + i, "nickname" + i, "010-1234-5678"));
  }
  List<Crew> firstPageCrews = crewRepository.findCrewByOffsetPage(0, 10);
  crewRepository.create(new Crew("name", "nickname", "010-1234-5678"));
  List<Crew> secondPageCrews = crewRepository.findCrewByOffsetPage(10, 10);

  Crew duplicateCrew = new Crew(11L, "name11", "nickname11", "010-1234-5678");
  assertThat(firstPageCrews).contains(duplicateCrew);
  assertThat(secondPageCrews).contains(duplicateCrew);
}

만약 C/U/D 빈도가 빈번할 경우 데이터가 중복 조회될 수도, 심지어는 원하는 데이터를 조회하지 못할 수 도 있게 됩니다.

따라서 데이터 셋이 많고, C/U/D 빈도가 높은 서비스는 cursor pagination을 사용합니다.

Cursor Pagination

커서 페이징은 테이블의 Primary key를 이용하여 페이지를 구합니다. 주로 무한 스크롤을 사용하는 서비스에서 해당 방식을 사용하고, 페이지 이동을 구현하는 것이 불가능합니다. 하지만 주로 스크롤에 따른 조회가 발생하기 때문에 이 단점은 크게 문제가 되지 않습니다.

select * from crew where id <= {curId} order by id desc limit {pageSize};

DB에서 인덱스를 이용하기 때문에 성능이 개선되고, id값으로 데이터를 조회하기 때문에 다음 페이지에서 데이터 중복 조회, 누락될 일이 없습니다.

 

데이터 응답시 JSON에 마지막 id값을 포함하여 보내주기만 한다면 쉽게 이 페이지네이션을 구현할 수 있습니다.