오늘은 페이징에 대해 작성해보겠다.
1. Offset based Pagination
offset pagination이란, page 혹은 offset/limit을 지정하여 데이터에서 pagination 된 결과를 조회하는 방식을 의미한다.
/article/?offset=10&limit=10
/article/?page=1
이와 같은 쿼리 파라미터를 전달하는 경우, 아래와 같이 쿼리로 데이터를 조회한다.
select * from article limit 10 offset 20 //20번 째 행부터 30행 까지 출력하라는 뜻
Offset 방식에는 단점이있다.
모든 컬럼의 내용을 읽어오기때문에
인덱스를 활용하는 방법인 Cursor 방법보다 느리다.
만약 DB메모리보다 스캔해야하는 데이터가 더 커지는 경우 오류가 나게 된다.
➡️ offset이 커질 수록 DB 부하 리스크는 더 커진다.
데이터를 부른 후 다른 사용자가 insert하거나 delete하면
중복된 데이터를 보는 경우가 생긴다
➡️ 예를 들어, 1페이지를 넘기는 순간 다른 데이터가 들어오게되면 2페이지에서 1페이지에서 봤던 데이터들을 보게될 수 있다. (정적인 데이터에 유리하다)
따라서 다루는 데이터셋이 많고 쓰기 이벤트가 빈번한 서비스에서는 Cursor Pagination을 사용한다.
2. Cursor based Pagination
Cursor Pagination은 조회가 중단된 마지막 페이지를 담은 커서라는 포인터(DataSet의 특정 레코드, 로우)를 파라미터로 전달한다 .
/article/?cursor=1&limit=10
SELECT * FROM article WHERE id <= {cursor} ORDER BY id DESC LIMIT {limit}
이 방법을 이용하면
인덱스가 적용된 값을 비교하기 때문에 테이블 풀스캔을 하지 않고 id 값으로 데이터를 조회하기 때문에, 데이터 쓰기가 빈번한 테이블이여도 다음 페이지네이션 조회 시 값이 누락되지 않는다!!!!!!!!
여기서 생기는 궁금증이 있었는데 그럼
과연 id 값은 누가 저장하고 있어야 하는지...! 에 관해서 이다.
설명을 위해 코드를 간단한 코드를 보겠다. (같이 스터디하는 언니가 설명해줬어요..언니 최고)
Post Entity를 위한 Repository
public interface PostRepository extends JpaRepository<Post, Long> {
List<Post> findByIdLessThanOrderByIdDesc(Long id, Pageable pageable);
//이때 Pageable 객체는 페이지 번호(page), 페이지 크기(size), 정렬 정보(sort)를 가짐
//이 메서드는 id가 주어진 값(cursor)보다 작은 post 객체를 id의 내림차순으로 조회하라는 의미이다!
}
Post Service
@Service
public class PostService {
private final PostRepository postRepository;
public PostService(PostRepository postRepository) {
this.postRepository = postRepository;
}
public List<Post> getPosts(Long cursor, int size) {
if (cursor == null) {
cursor = Long.MAX_VALUE;
}
return postRepository.findByIdLessThanOrderByIdDesc(cursor, PageRequest.of(0, size));
//이 부분은 Pageable 인터페이스의 구현체인 PageRequest 객체를 생성하는 부분이고 0은 페이지 번호, size는 페이지의 크기를 나타냄!
}
}
여기서 중요하게 봐야할 부분은 getPosts 매개변수 부분이다.
Long 타입의 cursor를 받아서 만약에 이 커서가 null 값이라면
(클라이언트에서 커서값에 대한 아무런 정보가 없거나 첫페이지여서) 가장 최신의 데이터부터 조회하도록 한다!
이후에는 클라이언트도 첫번째 요청을 통해 커서의 값을 파악할 수 있게 되므로 그 다음 커서값을 클라이언트가! 별도로 관리하고 결정하여 서버에 전달하는 방식인 것이다!
(서버에서 이 값을 별도로 저장해두거나 관리할 필요는 없다!)