[Spring/Kotlin] 커서 기반 페이지네이션

트러블 슛돌이·2023년 8월 17일

서론

이전에 페이징에 대해서 다룬 적이있다
이전에 다룬 페이징은 쪽지기능에 적용하기 위한 기능이었다
하지만 그 때 구현한 페이징 기법은 오프셋 기반 페이지네이션으로 쪽지 기능에서 사용하기엔 몇가지 문제점이 있었다
그래서 이를 보완하기 위해서 페이징 방식을 커서 기반 방식으로 변경하였다

오프셋 기반 페이지네이션 적용 : https://velog.io/@shameless8/SpringKotlin-Paging

오프셋 기반 방식 vs 커서 기반 방식

오프셋 기반 페이지네이션은 간단하게 조회를 시작할 기준점을 정하여 그 기준에 따라 페이지 단위로 구분하여 데이터를 보여주는 방식이다

오프셋 기반 방식에는 두 가지 문제점이 있었다

문제1 : 각 페이지를 요청하는 사이 데이터베이스에 변화가 생길 경우 중복 데이터 노출

예를 들어 게시판의 1페이지에서 20개의 게시물 목록을 보여주는데 그 사이에 5개의 게시물이 추가되었다
그리고 2페이지를 클릭했을 때는 어떻게 될까?
게시물은 1페이지부터 최신순으로 보이므로 1페이지에서는 방금 추가된 게시물 5개를 포함한 가장 최신 게시물들이 20개가 보여지고 2페이지에서는 1페이지에 있었던 마지막 5개의 게시물이 중복되어 보여진다

문제2 : dbms에서 offset 쿼리의 퍼포먼스 문제

말이 어렵지 실제로는 간단한 문제이다
오프셋 기반 페이지네이션은 row값이 아주 클 때 offset값이 올라갈 수록 쿼리의 효율성은 떨어진다

예를 들어서 limit가 10 offset이 1000이면 총 1010개의 데이터를 읽은 후에 1000개의 데이터는 버려야 하기 때문에 row값이 커질수록 성능이 떨어질 가능성이 커진다

커서 기반 페이지네이션

커서기반 페이지네이션의 방식을 간단하게 풀면 이렇다
클라이언트에게 보낸 마지막 row의 순서상 다름 row들을 n개 요청/응답하도록 구현하는 것이다
n개의 row를 스킵한 다음 10개를 주는 것이 아니라 마지막으로 보낸 row에 책갈피를 해놓은 후에 이 row부터 10개의 데이터를 주는 식이다

커서 기반 페이지네이션의 구현방법

이전에 블로그에서 다룬 오프셋 기반 페이지네이션은 쿼리의 offset과 limit를 사용하여 구현하였다 그렇다면 커서기반 페이지네이션은 어떤 것이 다를까?

본인이 사용한 예시를 보며 간단히 설명하겠다

 <select id="findDmListWithCursorBasedPaging" resultType="ResultDmDto">
       select id,
              IF(is_from_sender = #{isSender}, 1, 0) as is_me,
              content,
              time,
              is_read,
              room_id,
              visible
       from dm
       where room_id = #{room_id} AND
             #{pagingCursor} > id
       order by id desc
       LIMIT #{limit}
   </select>

이 코드에서 중요한 것은 바로 where절에있는 "#{pagingCursor} > id" 부분이다

비유를 사용하여 설명을 하자면, 파라미터로 받은 cursor의 값이 id보다 큰 곳에 책갈피를 두고 그 이후부터 limit페이지만큼을 넘겨 보여주는것이다

이 쿼리문에서는 cursor가 id값으로 사용이되었지만 이는 꼭 id값이 아니어도 된다

느낀점

쪽지 기능을 만들면서 사용됐던 offset기반 페이지네이션에 문제점이 다수 있다는 것을 알게되었고 이에 대한 해답으로 cursor기반 페이지네이션이 사용되고있다는것을 알게되었다

많은 문제를 해결할 수 있는 기법이기 때문에 매우 복잡할것 같았지만 오히려 offset기반 페이지네이션보다 훨씬 간단했다

느낀점이라고 하긴 뭐하지만 문제해결은 끝이 없는것같다

0개의 댓글