No Offset Paging 구현


offset 기반의 페이징

페이지 번호와 페이지 사이즈 기반으로 구현한 기존의 페이징 방식은

다음과 같이 페이지 번호가 하단에 출력된다.

page1




No Offset (Cursor based Pagination) 기반의 페이징

SNS 에서는 이 방식 보다는 다음과 같이 무한 스크롤을 가능하게 하는 No Offset 방식이 더 어울리며

No Offset 방식은 페이지 번호를 지정하는 방식보다 더 빠르다.

page


위 이미지를 보면 페이지 번호를 클릭해서 페이지가 넘어가는 게 아니라

스크롤이 페이지 끝에 닿았을 때 새로운 페이지가 로딩되는 것을 확인할 수 있다.



Q. 페이지 번호가 없는 방식을 No Offset이라 하는데 왜 더 빠를까?

A. 최신 컨텐츠가 우선으로 조회되어야 하는데 페이지 번호를 지정하는 쿼리는

앞에서 읽었던 행을 다시 읽어야 하기 때문에 뒤로 갈수록 느려진다.

No Offset 방식은 조회 시작 부분을 인덱스로 빠르게 찾아 매번 첫 페이지만 읽는다.



구현 코드

@Transactional(readOnly = true)
public List<Post> getPosts(Long userId, Long lastPostId, Pageable pageable) {
	if (lastPostId > 0) {
		return postRepository.findByUserIdAndIdLessThan(userId, lastPostId, pageable);
	}

	return postRepository.findByUserId(userId, pageable);
}


처음 조회하는 경우 lastPostId가 없기 때문에

findByUserId() 메서드를 통해 최신 게시글을 조회한다.

이후 호출되는 API 부터는 lastPostId 를 기준으로 조회한다.


@Repository
public interface PostRepository extends JpaRepository<Post, Long> {

	List<Post> findByUserId(Long userId, Pageable Pageable);

	List<Post> findByUserIdAndIdLessThan(@Param("userId") Long userId, @Param("id") Long lastPostId, Pageable Pageable);

	@Query(value = "SELECT p"
		+ " FROM Post p"
		+ " JOIN Follow f ON p.user.id = f.follower.id"
		+ " WHERE p.user.id = :userId"
		+ " AND  f.follower.id = :userId")
	List<Post> findByJoinFollow(@Param("userId") Long userId, Pageable pageable);

	@Query(value = "SELECT p"
		+ " FROM Post p"
		+ " JOIN Follow f ON p.user.id = f.follower.id"
		+ " WHERE p.user.id = :userId"
		+ " AND  f.follower.id = :userId"
		+ " AND p.id < :lastPostId")
	List<Post> findByJoinFollowAndLastIdLessThan(@Param("userId") Long userId, @Param("lastPostId") Long lastPostId, Pageable pageable);
}