Offset, No-Offset Pagination이란?(feat. Slice, Page)

예름·2025년 2월 26일
post-thumbnail

📍 Offset과 No-Offset Pagination의 차이점이 뭘까?
📍 또 Spring Data에서 제공하는 Page와 Slice의 차이점은 뭘까?

💡 Slice vs Page

먼저 Offset, No-Offset Pagination을 설명하기 전에 Slice와 Page에 대해 알아보겠다.

🔎 Slice

Slice는 Spring Data에서 제공하는 페이징 인터페이스 중 하나이다.

메서드는 다음과 같이 존재한다.

📌 대표적인 메서드

  • getNumber(): 현재 슬라이스의 번호를 반환
  • getSize(): 슬라이스의 크기를 반환
    ex) 예를 들어 슬라이스의 크기가 5개라면 5개씩 넘겨준다.
  • hasNext(): 다음 슬라이스가 존재하는지 반환

📌 SliceImpl

Page 생성자는 SliceImpl에 구현돼있다.
파라미터로 content, pageable, hasNext를 넘겨받는다.

SliceImpl은 Chunk를 상속받기 때문에 생성자에 아래와 같은 코드가 적혀있다.

super(content, pageable)

참고로 Chunk 클래스는 다음과 같이 있다.(현재 주제와 크게 중요하지 않으므로 가볍게만 보길 권장한다.)

🔎 Page

다음으로 Page에 대해 알아보겠다.

Page는 Slice 인터페이스를 상속받은 인터페이스로 Slice의 메서드에 empty(), getTotalPages(), getTotalElements() 메서드를 추가로 갖고 있다.

📌 추가된 메서드

  • empty(): 빈 페이지를 반환
  • getTotalPages(): 전체 페이지 수를 반환
  • getTotalElements(): 전체 데이터 수를 반환

→ 이 추가된 메서드로 인해 page를 찾는 쿼리 말고도 전체 데이터를 조회하는 쿼리(count 쿼리)가 한 번 더 발생한다.

📌 PageImpl

Slice와 마찬가지로 Page 생성자는 PageImpl에 구현돼있다.
파라미터로 content, pageable, total을 넘겨받는다.

🔎 Slice vs Page

비유를 들어 설명하면 Page는 책의 전체 페이지 수를 알고 읽는 느낌, Slice는 책갈피를 꽂아놓고 다음 페이지가 있는지만 확인하는 느낌이다.

❓ Slice와 Page에 있는 Pageable은 뭘까?

Pagination 정보(아래 그림 참고)를 갖고 있는 인터페이스이다.

Slice, Page 모두 페이징한 데이터(content), 페이지 정보(pageable)를 클라이언트에게 반환한다.


💡 Offset vs No-Offset Pagination

이제 본격적으로 Offset과 No-Offset Pagination을 설명하겠다.

🔎 Pagination이란?

Pagination은 데이터를 한 번에 조회하는 것이 아닌 페이지로 나눠서 조회하는 방법을 말한다.

🔎 Offset Pagination

Offset Pagination은 말 그대로 오프셋을 이용해서 페이징을 하는 방법이다. 가장 고전적인 Pagination 방법이다.

클라이언트로부터 size, offset을 받으면 offset 만큼의 데이터를 건너뛰고 size 만큼의 데이터를 조회하여 클라이언트에게 넘겨준다.

예를 들어보자

SELECT * FROM table
ORDER BY {정렬조건}

데이터베이스에 100,000개의 데이터가 존재하고, size = 10, offset = 0이면 0개의 데이터를 건너뛰고 10개의 데이터를 조회한다. 그럼 다음은 offset = 10으로 바뀌고 이런식으로 전체 데이터를 조회한다.

극단적으로 데이터가 1억 개 있는 테이블에서 offset 9999는 9999개의 데이터를 먼저 읽고 10개를 반환하는 방식이므로 성능이 떨어진다.

📌 구현

Offset Pagination은 전체 데이터의 수와 전체 페이지의 수를 알아야 하기 때문에 Page로 구현한다.

⚠️ 치명적인 단점?

Offset Pagination은 간단해 보이지만 크게 두 가지의 단점이 있다.

1. 데이터 중복 조회 & 데이터 조회 누락

만약 데이터가 20개가 있고, offset이 3이라고 해보자

첫 페이지를 조회하고 데이터가 4개가 추가됐는데 정렬 결과, 2개의 데이터(21, 22)가 2번 데이터 뒤에 있다고 가정해보자

첫 페이지를 조회한 후에는 다음과 같을 것이다.
(offset = 0)

데이터첫 번째
1o
2o
3o
4-
5-
......

두 번째 페이지를 조회한 후에는 다음과 같이 조회된다.
(offset = 3)

데이터두 번째
1-
2-
‼️ 21 ‼️-
22o
‼️ 3 ‼️o
4o
5-
......

offset이 3이므로 3개의 데이터를 건너뛰고 그 다음 데이터를 조회한다.

→ 21번 데이터는 새로 추가됐지만 offset으로 인해 조회되지 못하고, 3번 데이터는 첫 페이지에서 조회됐음에도 한 번 더 조회된다.

2. 데이터의 수가 커질수록 조회 성능 떨어짐

n번째 페이지라고 했을 때 offset = n * size가 성립한다.

따라서 데이터의 수가 커질수록 건너뛰어야 하는 데이터의 수(offset)도 점점 커지기 때문에 조회 성능이 떨어지게 된다.

🔎 No-Offset Pagination

No-Offset Pagination은 offset을 이용하지 않고 마지막으로 조회한 데이터의 아이디와 size를 이용하여 조회한다. Cursor-Based Pagination 이라고도 부른다.

데이터가 아이디를 기준으로 내림차순으로 정렬돼있고 마지막으로 조회한 데이터의 아이디가 10이라고 가정하면, 데이터의 아이디가 10보다 작은 데이터만을 조회하는 방법이다.

SELECT * FROM table
WHERE id < cursorId
ORDER BY {정렬조건} DESC

예를 들어 id = 100까지 조회한 후 새로운 데이터(id=105)가 추가되었을 때, 다음 조회는 id < 100 기준으로 진행되므로 새 데이터도 놓치지 않고 조회한다.

📌 구현

No-Offset Pagination은 전체 데이터의 개수와 전체 페이지의 개수를 알 필요가 없기 때문에 Slice로 구현한다. 물론 Page로 구현해도 되지만 전체 데이터 조회 쿼리가 실행되므로 불필요한 작업이 수행되어 성능이 떨어진다.

✨ 장점

Offset Pagination 방법과 달리 offset 만큼 데이터를 조회하지 않아도 되므로 일반적으로 Offset Pagination 방법보다 조회 성능이 좋다.

⚠️ 주의할점

WHERE 절에서 조회할 때 조회 기준이 UNIQUE 하지 않으면 일부 데이터가 건너뛰거나 중복 조회될 위험이 있으므로 조회 기준은 UNIQUE 해야 한다.

❓ Offset vs No-Offset, 어떤 방식을 선택해야 할까?

No-Offset 방식이 성능상 유리한 것은 사실이지만, 모든 상황에서 Offset Pagination보다 나은 것은 아니다.

✅ Offset 방식이 적합한 경우

  • 페이지별 이동(예: "이전 페이지" / "다음 페이지" 버튼을 통한 탐색)이 필요한 경우
  • 사용자가 특정 페이지 번호로 직접 이동할 수 있어야 하는 경우
  • 전체 데이터 개수를 조회하는 것이 중요한 경우

✅ No-Offset 방식이 적합한 경우

  • 대량의 데이터를 다룰 때 페이징 성능을 최적화해야 하는 경우
  • 무한 스크롤 방식(인스타그램, 트위터 같은 피드)에 적합한 경우
  • 데이터 중복 조회를 방지하고 최신 데이터를 빠르게 가져와야 하는 경우

따라서 서비스의 특성에 따라 Offset 방식과 No-Offset 방식을 적절히 선택하는 것이 중요하다.


참고문헌
🔗 https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/domain/package-summary.html
🔗 https://jojoldu.tistory.com/528
🔗 https://giron.tistory.com/m/131
🔗 https://velog.io/@znftm97/%EC%BB%A4%EC%84%9C-%EA%B8%B0%EB%B0%98-%ED%8E%98%EC%9D%B4%EC%A7%80%EB%84%A4%EC%9D%B4%EC%85%98Cursor-based-Pagination%EC%9D%B4%EB%9E%80-Querydsl%EB%A1%9C-%EA%B5%AC%ED%98%84%EA%B9%8C%EC%A7%80-so3v8mi2

profile
안정적인 쳇바퀴를 돌리는 삶

2개의 댓글

comment-user-thumbnail
2025년 2월 26일

고수시네요.. 오늘도 제 실력에 부끄러움을 느낍니다.

1개의 답글