커서 기반 페이지네이션(cursor-based pagination)은 데이터베이스에서 대량의 데이터를 효과적으로 페이징하는 기술 중 하나입니다. 이 기술은 무한 스크롤과 같은 사용자 경험을 구현하는 데 유용합니다. 커서 기반 페이지네이션은 다음과 같이 작동합니다:
데이터베이스 쿼리 작성: 처음 페이지 로딩 시에는 데이터베이스에서 처음 N개의 아이템을 가져오는 쿼리를 작성합니다. 이 때, 쿼리는 일반적으로 LIMIT
또는 TOP
절과 함께 사용하여 결과 집합의 크기를 제한합니다.
커서 생성: 처음 데이터를 로드한 후, 마지막으로 로드한 아이템의 고유한 식별자(예: ID)를 기록합니다. 이것이 다음 페이지를 가져올 때 사용되는 커서(cursor) 역할을 합니다.
다음 페이지 요청: 사용자가 무한 스크롤을 하면, 백엔드에 다음 페이지를 요청합니다. 이때, 이전 페이지에서 기록한 마지막 아이템의 식별자를 함께 보냅니다.
커서 기반 쿼리 실행: 백엔드는 이전 페이지에서 받은 커서를 사용하여 데이터베이스 쿼리를 작성합니다. 이 쿼리는 이전 페이지에서 마지막 아이템 이후의 아이템들을 가져오는 역할을 합니다.
결과 반환: 데이터베이스에서 새로운 페이지의 데이터를 가져온 후, 이를 클라이언트에 반환합니다. 클라이언트는 이 결과를 현재 페이지에 추가하여 사용자에게 무한 스크롤 경험을 제공합니다.
이러한 커서 기반 페이지네이션의 장점은 다음과 같습니다:
주의할 점은 커서 값이 고유하고 순서가 있는 필드(예: ID, 타임스탬프)를 기반으로 하여야 한다는 것입니다. 또한, 커서가 변경되면 이전 페이지의 데이터를 검색하는 것이 어렵기 때문에 임의의 페이지로 이동하는 것은 지원되지 않습니다.
마지막으로, 페이지네이션을 구현할 때 클라이언트와 서버 간의 커뮤니케이션 프로토콜을 정의하고, 데이터베이스에서 쿼리를 작성하여 커서 기반 페이지네이션을 구현해야 합니다.
내가 마주한 상황 : 무한스크롤이지만, getAll 처럼 받아왔다.
문제가 되는 상황 : SELECT * FROM lesson 과 같은 쿼리는 enum용도의 테이블이 아니라면 백엔드 선에서 컷해야 합니다. 무조건 LIMIT과 함께 사용하여 페이지네이션 형태로 제공해야 합니다.
개선 방법 : cursor-based pagination 구현
페이지네이션(Pagination): 페이지네이션은 웹 애플리케이션 또는 웹 사이트에서 대량의 데이터나 긴 목록을 여러 페이지로 나누어 표시하는 기술을 가리킵니다. 대개는 한 페이지에 표시할 수 있는 아이템 수에 제한을 두고, 이러한 목록을 여러 페이지로 분할하여 사용자가 쉽게 탐색하고 관리할 수 있게 합니다.
서버 입장에서도, 클라이언트 입장에서도 특정한 정렬 기준에 따라 지정된 갯수의 데이터를 가져오는 것이 필요하다. 이걸 페이지네이션이라고 한다.
페이지네이션의 필요성: 페이지네이션은 다음과 같은 이유로 필요합니다.
커서 기반 페이지네이션(cursor-based pagination): 커서 기반 페이지네이션은 페이지마다 커서 또는 특정 행의 위치를 사용하여 데이터를 나누는 페이지네이션 기술입니다. 이전 페이지에서 어떤 위치에서 멈췄는지를 가리키는 커서를 사용하여 다음 페이지의 데이터를 가져옵니다. 이는 무한 스크롤과 같은 기능을 구현하는 데 매우 유용합니다.
다른 유형의 페이지네이션: 페이지네이션에는 다양한 유형이 있으며, 몇 가지 일반적인 유형은 다음과 같습니다.
각 페이지네이션 유형은 특정 사용 사례나 사용자 경험을 위해 선택되며, 애플리케이션의 요구 사항 및 디자인에 따라 다릅니다.
오프셋 기반 페이지네이션(Offset-Based Pagination):
LIMIT
및 OFFSET
과 같은 SQL 절을 사용하여 원하는 페이지의 데이터를 가져옵니다.커서 기반 페이지네이션(Cursor-Based Pagination):
예를 들어, 오프셋 기반 페이지네이션은 "첫 번째 페이지에 10개의 항목을 표시"라는 방식으로 데이터를 가져오는 반면, 커서 기반 페이지네이션은 "이전 페이지의 마지막 항목부터 시작해 다음 페이지에 10개의 항목을 표시"하는 방식으로 데이터를 가져옵니다. 커서 기반 페이지네이션은 대량 데이터와 무한 스크롤 기능을 구현하는 데 매우 효과적입니다.
커서 기반 페이지네이션 예시
커서 기반 페이지네이션을 이해하기 위해 "포스트(Post)" 데이터의 리스트를 페이지네이션하는 간단한 예를 들어보겠습니다. 각 포스트는 고유한 ID와 컨텐츠를 가집니다.
public class Post {
private Long id;
private String content;
// 생성자, getter, setter, 등 필수 메서드 구현
}
@RestController
@RequestMapping("/posts")
public class PostController {
@Autowired
private PostService postService;
// 페이지네이션을 위한 API 엔드포인트
@GetMapping("/paginate")
public ResponseEntity<List<Post>> getPosts(@RequestParam(value = "cursor", required = false) Long cursor, @RequestParam(value = "limit", defaultValue = "10") int limit) {
List<Post> posts = postService.getPosts(cursor, limit);
return ResponseEntity.ok(posts);
}
}
@Service
public class PostService {
@Autowired
private PostRepository postRepository;
public List<Post> getPosts(Long cursor, int limit) {
if (cursor == null) {
// 첫 번째 페이지
return postRepository.findTopByOrderByIdDesc(limit);
} else {
// 이전 페이지의 마지막 포스트 ID부터 시작
List<Post> posts = postRepository.findNextPage(cursor, limit);
return posts;
}
}
}
public interface PostRepository extends JpaRepository<Post, Long> {
@Query("SELECT p FROM Post p WHERE p.id < :cursor ORDER BY p.id DESC")
List<Post> findNextPage(@Param("cursor") Long cursor, @Param("limit") int limit);
List<Post> findTopByOrderByIdDesc(int limit);
}
이 예제에서, /posts/paginate
API는 cursor
파라미터를 사용하여 이전 페이지의 마지막 포스트 ID를 지정할 수 있으며, limit
파라미터를 사용하여 한 페이지에 표시할 포스트 수를 지정할 수 있습니다. 첫 번째 페이지를 요청할 때는 cursor
를 생략하거나 null
로 설정합니다. 이전 페이지의 마지막 포스트 ID를 알고 있으면 그 값을 cursor
로 사용하여 다음 페이지를 요청할 수 있습니다.
이런 식으로 커서 기반 페이지네이션을 구현하면 페이지간의 일관성을 유지하면서 대량의 데이터를 효과적으로 처리할 수 있습니다.
참고)
https://www.devjoon.com/41
내 머리가 안좋아서ㅣ,,ㅜ