페이지네이션을 처리하는 방법 : offset 기반 vs. cursor 기반

후투티·2025년 5월 3일

잊지마!

목록 보기
17/20

offset 기반 페이지네이션

  • 개념 : 몇 번째 페이지인지 기준으로 데이터를 잘라 가져오는 방식

    • 요청 예시 : GET /엔드포인트?page=4&size=20
      -> 5번째 페이지의 20개 가져와줘
  • Spring Data JPA의 Pageable 인터페이스로 구현이 가능하다.

  • 응답 정보
    - 현재 페이지 번호
    - 전체 페이지 수
    - 전체 데이터 수
    - 첫번째/마지막 페이지 여부

  • 단점

    데이터가 많을수록 느려질 수 있다.
    정적인 데이터에는 적절하지만, 실시간으로 변경되는 데이터가 있는 경우 문제가 생긴다.

    • 데이터 추가/삭제로 인한 "밀림 현상"이 일어날 수 있다.
    • 예를 들어, 1~3번 데이터를 본 후 다음 페이지로 넘어갔는데,
      4번 데이터가 빠지고 5번부터 나올 수 있다.

이처럼 데이터가 실시간으로 변경될 수 있다면 offset 방식은 데이터 누락 문제로 이어질 수 있다.
예를 들어, 관리자 권한으로 대기 ➔ 승인으로 상태를 변경할 시, 실시간으로 데이터가 빠지는 등, 데이터 변동이 잦기 때문에
정확한 이어보기와 부드러운 무한스크롤을 위해 cursor 기반 페이지네이션을 사용하는 게 낫다.


cursor 기반 페이지네이션

  • 개념 : 특정 데이터(커서)를 기준으로 그 이후의 데이터를 가져오는 방식
    즉, 마지막으로 본 데이터를 기준으로 삼고, 그 다음 데이터를 가져오는 방식이다.

  • 응답에 포함되는 정보:

    • 현재 응답 데이터 목록
    • 다음 요청에 사용할 커서 값
    • 다음 페이지 존재 여부 (hasNext)
  • 요청 :

    최초 요청 시: 커서 없이 요청 → 첫 번째 커서 기준으로 응답
    이후 요청 시: 마지막으로 받은 커서 값을 기준으로 다음 데이터 요청

  • 특징 :

    • 데이터가 많아도 성능이 일정하다
    • page 개념이 없고, "어디서부터 이어받을지"만 신경을 쓴다.
      • 몇 번째 페이지를 요쳥하는 게 아니라 어디까지 봤는지를 기억해서 요청하는 것
  • 주의할 점

    • 커서로 사용하는 값은 정렬 가능한 고유한 값이어야 함

    • 커서 값이 동일한 데이터가 여러 개 있을 경우, 중복 조회되거나 누락되는 문제 발생 가능
      → 두 번째 정렬 기준 필요 (ex. 첫 번째 커서 - createdAt, 두 번째 커서 - id)

      • 이런 식으로 진행하려면
        첫 요청에는 첫 번째 정렬기준(커서)를 필수로 넣어야 하고,
        두 번째 요청부터는 첫 번째 요청에서부터 응답받은 첫 번째, 두 번째 정렬기준(커서)를 모두 필수로 넣어줘야한다.
        그리고 이러한 과정을 hasNext가 true인 동안 반복해서 호출해준다.
  • 구현 방식 :
    Page 처럼 공식적으로 정해진 클래스가 없으니
    각자의 서비스에 맞게 CursorPageResponse 커스텀 응답 클래스를 정의해서 사용해야 한다.

    @Getter
    @AllArgsConstructor
    public class CursorPageResponse<T> {
    
    private List<T> content; // 데이터들
    private LocalDateTime nextCreatedAt; // 마지막 인증의 생성시간
    private Long nextCertificationId; // 마지막 인증의 ID
    private boolean hasNext; // 더 많은 데이터가 있는지 여부
    }
    
profile
모르는 건 모른다고 하는 사람

0개의 댓글