커서 기반 페이징에 대해 자세히 알아보기 전에 페이지네이션에 대한 간단한 맥락을 살펴보겠습니다:
페이지네이션은 소프트웨어 응용 프로그램, 웹사이트 및 데이터베이스에서 사용되는 기술로, 콘텐츠를 이산적인 페이지로 나누어 대량의 데이터를 쉽게 표시, 관리 및 탐색할 수 있게 합니다. 한꺼번에 모든 데이터를 로드하고 표시하는 대신 페이지네이션은 콘텐츠를 관리 가능한 작은 청크로 나누어 주는 데 도움이 됩니다.
응용 프로그램의 사용 사례에 따라 페이지네이션 기술은 다양하게 활용될 수 있습니다. 우리가 모두 관찰한 일반적인 사용 사례 중 하나는:
- 페이지 번호 옆에 "이전" 또는 "다음" 버튼을 제공합니다.
- 사용자가 화면을 무한으로 스크롤할 수 있는 인피니트 스크롤링을 제공합니다. 사용자가 뷰포트의 하단에 도달하면 새로운 콘텐츠가 지속적으로 추가됩니다. (소셜 미디어 피드, 전자상거래 검색 결과...)
위와 같은 페이지네이션 사용 사례는 두 가지 방법으로 구현할 수 있습니다:
- 오프셋 기반 접근 방식
- 커서 기반 접근 방식
오프셋 기반 접근 방식은 간단하고 쉽게 구현할 수 있으며 빈도가 낮게 업데이트되는 정적 데이터 세트에 적합합니다.
클라이언트 측 매개변수로 레코드 수를 나타내는 Limit 및 데이터 검색을 시작할 위치를 나타내는 Offset 또는 PageNumber가 필요합니다.
예를 들어, 1 페이지의 레코드를 가져오려면 Offset = 0, Limit = 10으로 설정합니다.
SELECT * FROM records LIMIT 10 OFFSET 0;
10 페이지의 레코드를 가져오려면 Offset = 90, Limit = 10으로 설정하면 됩니다.
SELECT * FROM records LIMIT 10 OFFSET 90;
Offset 기반 접근 방식의 단점:
- Offset 값이 증가할수록 이 접근 방식은 적합하지 않습니다. Offset이 증가함에 따라 데이터베이스는 원하는 결과를 반환하기 전에 증가하는 수의 행을 건너뛰어야 합니다. 이는 특히 대규모 데이터 세트의 경우 쿼리 성능이 느려질 수 있습니다.
- 많은 수의 행을 건너뛰는 것은 CPU 및 메모리 리소스를 더 많이 소비하여 프로세스를 비효율적으로 만들고 데이터베이스 서버의 성능에 영향을 줄 수 있습니다.
- 자주 업데이트되는 데이터의 경우, 현재 페이지창이 시간이 지난 후 부정확할 수 있습니다. 사용자가 피드에서 첫 10개 레코드를 가져왔다고 상상해보세요. 시간이 지난 후 5개의 레코드가 추가되었습니다. 사용자가 피드 맨 아래로 스크롤하고 페이지 2를 가져오면 원래 페이지 1의 동일한 레코드가 가져와질 것이며, 사용자는 중복된 레코드를 볼 것입니다.
이제 페이지네이션에 대해 이해했으며 다양한 접근 방법, 특히 Offset 기반 접근 방식의 이점 및 단점을 알게 되었습니다. 이제 Cursor-Based 페이지네이션에 대해 더 깊이 파고들어보고 사용 사례와 장점을 이해해보겠습니다.
커서 기반 페이징은 데이터 세트의 특정 레코드에 대한 고유 식별자(커서)를 사용합니다. "11번부터 20번까지의 레코드를 제공해줘"라는 식으로 말하는 대신 "특정 아이템 다음부터 10개의 아이템을 제공해줘"라고 요청합니다.
이 방법은 대량의 데이터 세트를 더 세련되게 처리하고, 특히 동적으로 자주 변경되는 데이터를 처리할 때 더 효율적일 수 있습니다.
- 최초 API 호출에서, 클라이언트(프론트엔드)는 제한만 제공합니다. API는 요청한 레코드 수를 반환하고, 동시에 데이터 세트에서 마지막 아이템을 가리키는 커서도 제공합니다.
- 이후 API 호출에서, 클라이언트는 이전 API 응답에서 받은 제한과 커서 값을 모두 제공해야 합니다.
- 서버는 이 커서 값을 사용하여 데이터베이스에서 레코드를 검색합니다. 이 고유 식별자인 커서 값은 아이템을 빠르게 식별하고 그 아이템의 이전이나 이후에 요청한 수의 레코드를 반환합니다.
소셜 미디어 피드의 예시를 들어보겠습니다. 데이터베이스에 수백만 개의 게시물이 존재하고 각 게시물에는 고유한 ID가 있습니다.
초기 호출 시에는 limit만 보내요!
- 1페이지 레코드 가져오기: Limit = 10
/* 백그라운드 SQL 쿼리 */
SELECT * FROM posts
ORDER BY post_id
LIMIT 10;
이렇게 하면 처음 10개의 게시물과 함께 커서 값(마지막 게시물의 id)을 받아요.
/* API 응답 */
{
"pagination": {
"size": 10,
"cursor": "post_10"
},
"results": [
{
"id": "post_1",
"author": {
"id": "123",
"name": "John Doe"
},
"content": "I got a new car",
"image": "https://www.example.com/newcar.jpg",
"reactions": {
"likes": 20,
"haha": 15
},
"created_time": 1620639583
}
// ... 더 많은 게시물들.
]
}
이 후의 호출에서는 이 cursor 값을 및 limit을 보내야 합니다.
- 2번째 페이지 레코드를 가져오기: Limit = 10, cursor: “post_10”
SELECT * FROM posts
WHERE post_id > "post_10"
ORDER BY post_id
LIMIT 10;
post_10이후에 나타나는 포스트 10개를 우리에게 반환하며, 그에 따라 커서 값(마지막 포스트의 id)도 표시됩니다:
/* API 응답 */
{
"pagination": {
"size": 10,
"cursor": "post_20"
},
"results": [
{
"id": "post_11",
"author": {
"id": "234",
"name": "Chidanandan"
},
"content": "새로운 강아지를 입양했어요",
"image": "https://www.example.com/newdog.jpg",
"reactions": {
"likes": 20,
"haha": 15
},
"created_time": 1620639583
}
// ... 더 많은 포스트들.
]
}
이전 페이지를 요청할 때, 데이터베이스에게 커서보다 작은 레코드를 보내도록 요청합니다.
SELECT * FROM posts
WHERE post_id < "post_10"
ORDER BY post_id
LIMIT 10;
에지 케이스 처리:
API를 호출하지 않고 데이터가 없는 경우를 확인하기 위해 cursor 대신 next_cursor를 사용할 수 있습니다. 여기서 next_cursor는 데이터베이스에서 다음 요소의 고유한 ID를 나타냅니다. 예를 들어, 10개의 요소를 요청하면 next_cursor 값은 post_11이 post_10 대신 될 것입니다.
이를 통해 우리는 이후에 추가적인 포스트가 있는지 여부를 사전에 알 수 있습니다. 응답에서 next_cursor 값이 비어 있다면 현재 페이지가 마지막 페이지임을 결론지을 수 있습니다.
Cursor 기반 접근법의 단점:
- 커서 기반 페이징은 사용자가 임의의 페이지로 직접 이동하는 것을 허용하지 않습니다 (예: "페이지 5로 이동"). 이는 사용자가 특정 페이지로 직접 이동할 것으로 기대하는 애플리케이션에서 제한이 될 수 있습니다.
- 커서 기반 페이징은 오프셋 기반 페이징에 비해 더 복잡하게 구현됩니다. 요청 사이의 커서를 처리하고 상태를 관리하는 추가적인 로직이 필요합니다.
커서 기반 페이징을 사용하는 경우:
- 대량 데이터 세트에 대해 효율적입니다. 큰 숫자의 행을 건너뛸 때 발생하는 성능 비용을 피하기 때문입니다.
- 데이터 세트가 자주 업데이트되는 경우 (예: 새 레코드가 추가되거나 삭제됨), 커서 기반 페이징은 고유 식별자에 집중하고 데이터 변경 문제를 피함으로써 일관된 결과를 제공합니다.
- 소셜 미디어 피드, 뉴스 기사 등 콘텐츠를 지속적으로 스크롤하는 사용자가 있는 애플리케이션에 이상적입니다.
오프셋 기반 페이징을 사용하는 경우:
- 성능이 중요하지 않은 작은 데이터셋의 경우 오프셋 기반 페이징은 간단하고 구현하기 쉽습니다.
- 데이터가 자주 변경되지 않는 경우 오프셋 기반 페이징은 충분하며 일관성 문제를 걱정할 필요 없이 더 간단하게 사용할 수 있습니다.
- 사용자가 특정 페이지 번호로 이동해야 하는 경우(예: "5 페이지"), 오프셋 기반 페이징은 임의의 페이지에 직접 액세스할 수 있어 특정 시나리오에서 더 사용자 친화적입니다.
올바른 페이징 방법을 선택하는 것은 특정 애플리케이션 요구사항과 데이터 특성을 기반으로 이러한 요소를 균형있게 고려하는 것에 달려 있습니다.
내 문서를 https://chidanandan.medium.com/에서 확인해주세요.