Offset/Cursor based Pagination

jkeum·2024년 5월 2일

Spring Boot

목록 보기
3/4

Pagination

페이지네이션(Pagination)은 웹 개발에서 데이터나 콘텐츠를 작은 페이지 단위로 나누어 보여주는 기술이다.
이 방법을 사용하면 사용자는 전체 데이터셋을 한 번에 로드하지 않고, 필요한 부분만을 순차적으로 또는 선택적으로 조회할 수 있다.

사용하는 이유:

  • 성능 향상: 모든 데이터를 한 번에 로드하려 하면 서버에 많은 부담을 줄 수 있으며, 네트워크 지연, 브라우저 렌더링 지연 등의 문제를 일으킬 수 있다. 페이지네이션을 사용하면 요청 시간과 데이터 로딩 시간을 줄일 수 있어, 전체적인 애플리케이션 성능을 향상시킬 수 있다.

  • 사용자 경험 향상: 사용자가 한번에 처리해야 할 정보의 양이 줄어들어, 정보를 찾고 이해하는 데 더 쉽고 효과적이다. 또한, 페이지네이션은 데이터를 체계적으로 구성하여 사용자가 원하는 데이터를 찾기 쉽게 도와준다.

  • 서버 리소스 최적화: 서버는 요청받은 페이지에 해당하는 데이터만 처리하면 되기 때문에, 자원 사용을 최적화할 수 있다. 이는 대용량 데이터 처리에 특히 중요한 장점이다.

사용하는 경우:

  • 대용량 데이터 처리: 데이터베이스에 수백만 건의 데이터가 있는 경우, 사용자가 웹 페이지를 통해 결과를 조회할 때 모든 데이터를 한 번에 로드하는 것은 비효율적이다. 페이지네이션을 사용하면 데이터를 적절한 크기로 분할하여 제공할 수 있다.

  • 목록형 UI: 뉴스 사이트, 블로그, 검색 엔진, 전자 상거래 사이트 등 목록 형태로 많은 양의 콘텐츠를 제공하는 사이트에서 페이지네이션은 필수적이다.

  • 리포트 및 대시보드: 분석 리포트나 대시보드를 통해 큰 데이터 셋을 보여줄 때 페이지네이션은 데이터를 여러 페이지에 걸쳐 요약하여 보여줄 수 있는 유용한 방법이다.

오프셋 기반 페이지네이션

오프셋 기반 페이지네이션은 가장 전통적인 형태로, 특정 "오프셋"에서 시작하여 지정된 수량의 데이터 항목들을 반환한다. 이 방식은 SQL 쿼리에서 LIMITOFFSET 구문을 사용해 구현할 수 있다.

장점:

구현이 간단하고 이해하기 쉽다.
사용자가 특정 페이지로 직접 점프할 수 있다.

단점:

큰 데이터셋에서 높은 오프셋 값을 사용할 때 성능 문제가 발생할 수 있다.
즉, 뒤쪽 페이지로 갈수록 쿼리 성능이 저하된다.

오프셋 기반 페이지네이션의 성능 문제

오프셋 기반 페이지네이션에서 성능 문제의 핵심은 데이터베이스가 "건너뛰어야 하는" 행의 수에 있다.
즉, 사용자가 요청하는 페이지 번호가 높아질수록 데이터베이스는 더 많은 행을 건너뛰어야 하고, 이는 쿼리 성능 저하로 이어진다.

예시

온라인 쇼핑몰의 상품 데이터베이스가 1,000,000개의 상품 정보를 저장하고 있을 때,
사용자가 100번째 페이지의 데이터를 요청한다고 가정한다(각 페이지에 100개의 상품을 보여주는 경우).

SELECT * FROM products ORDER BY created_at LIMIT 100 OFFSET 9900;

이 경우, 데이터베이스는 처음 9900개의 행을 건너뛰고, 그 다음 100개의 행을 반환해야 한다.
페이지 번호가 증가함에 따라 OFFSET 값도 증가하고, 따라서 데이터베이스가 무시해야 하는 행의 수도 증가한다.

성능 문제

데이터베이스가 건너뛰어야 하는 행의 수가 많을수록 쿼리의 실행 시간도 길어진다.
이는 데이터베이스가 실제로 데이터를 가져오기 전에 많은 수의 행을 읽어야 하기 때문이다.
실제로, OFFSET의 값이 크면 데이터베이스는 많은 I/O 작업을 수행해야 하고, 이는 전반적인 성능 저하를 초래한다.

성능 개선 방안

  • 인덱스 최적화: 쿼리가 사용하는 컬럼(예: created_at)에 인덱스를 추가하여, 데이터베이스가 데이터를 더 빠르게 찾을 수 있도록 한다.
  • 커서 기반 페이지네이션 사용: 오프셋 대신 커서를 사용하면, 데이터베이스는 항상 고정된 수의 행만 건너뛰고 데이터를 가져오므로, 큰 데이터셋에서도 일관된 성능을 유지할 수 있다.
  • 데이터 캐싱: 자주 요청되는 데이터 페이지를 캐시에 저장하여, 데이터베이스 쿼리 없이 빠르게 데이터를 제공할 수 있다.

커서 기반 페이지네이션

커서 기반 페이지네이션은 데이터를 페이지로 분할할 때 '커서'로 불리는 지정된 포인터를 사용하는 방법입니다. 이 커서는 데이터베이스 내의 특정 위치를 가리키며, 주로 다음 데이터 페이지를 효율적으로 로딩하는 데 사용됩니다. 커서 기반 페이지네이션은 특히 대용량 데이터를 다룰 때, 또는 데이터 변경이 빈번하게 발생하는 환경에서 유용합니다. 이 방식은 페이지네이션의 성능을 향상시키고, 데이터 중복이나 누락 없이 일관된 데이터 보기를 제공합니다.

커서 기반 페이지네이션의 작동 원리

커서 기반 페이지네이션에서 커서는 일반적으로 데이터의 고유한 속성(예: ID, timestamp)을 사용하여 설정된다.
이 속성은 데이터베이스 테이블에서 색인(indexed)되어 있어야 하며, 데이터를 순서대로 정렬하는 데 사용된다.

기본 쿼리 구조

SELECT * FROM 데이터테이블
WHERE 커서조건
ORDER BY 커서기준
LIMIT 페이지크기;

여기서 커서조건은 일반적으로 이전 페이지의 마지막 데이터 포인트를 기준으로 한다.
예를 들어, ID를 커서로 사용하는 경우, 마지막으로 조회된 데이터의 ID를 기준으로 그 이후의 데이터를 조회하게 된다.

커서의 값과 로직

  • 초기 요청:
    첫 페이지 로드 시 일반적으로 커서 값을 제공하지 않는다.
    이 경우, 백엔드 시스템은 가장 최근의 데이터(예: 가장 최근에 생성된 게시물)를 기준으로 첫 페이지 데이터를 반환한다.
    초기 요청에서는 커서 값을 명시적으로 '최대값'으로 설정하거나, 아예 보내지 않을 수 있다.
    이렇게 하면 데이터베이스는 가장 최근 데이터부터 시작하여 페이지에 해당하는 데이터를 제공할 수 있다.

  • 이후 요청:
    다음 페이지 요청 시, 클라이언트는 마지막으로 받은 데이터 세트 중 마지막 항목의 커서 값을 다음 요청의 커서로 제공한다.
    예를 들어, 게시물의 created_at 필드나 id 필드가 커서로 사용될 수 있다.
    이 값은 이전 요청에서 받은 마지막 게시물의 해당 필드 값이다.
    클라이언트가 이 커서 값을 백엔드에 전송하면, 백엔드는 이 커서보다 오래된(낮은) 게시물을 반환하도록 쿼리를 수행한다.

예시

상품 데이터베이스가 있고, 각 상품에는 유일한 idcreated_at 타임스탬프가 있다고 가정한다.
사용자가 상품 목록을 최신순으로 조회하고자 할 때, 커서 기반 페이지네이션을 사용하는 쿼리는 다음과 같이 작성될 수 있다.

SELECT * FROM products
WHERE created_at < '2021-01-01T00:00:00' -- 이전 페이지의 마지막 상품 타임스탬프
ORDER BY created_at DESC
LIMIT 10;

이 쿼리는 2021년 1월 1일 이전에 생성된 상품 중에서 최신 10개의 상품을 반환한다.

장점

  1. 성능: 대규모 데이터셋에서 효율적으로 작동한다. 커서의 위치를 기준으로 데이터를 즉시 조회할 수 있기 때문에, 오프셋 기반 페이지네이션에서 발생할 수 있는 성능 저하 문제를 피할 수 있다.
  2. 일관성: 데이터 추가나 삭제가 자주 발생하는 경우에도 사용자가 일관된 데이터를 볼 수 있다. 데이터가 추가되어도 이미 로드된 페이지에 영향을 주지 않는다.

단점

  1. 사용자 인터페이스: 사용자가 임의의 페이지로 직접 점프하기 어렵다. 대부분 다음 페이지나 이전 페이지로의 순차적 이동만 지원한다.
  2. 구현 복잡성: 오프셋 기반 페이지네이션에 비해 구현이 다소 복잡할 수 있으며, 커서를 관리하고 유지하는 로직이 필요하다.

커서 기반 페이지네이션은 API 설계에서 매우 흔하게 사용되며, 특히 RESTful API나 GraphQL API에서 클라이언트와 서버 간의 효율적인 데이터 교환을 위해 자주 활용된다.
이 방식은 성능과 확장성을 고려할 때 많은 장점을 제공하며, 대용량 데이터를 다루는 웹 서비스나 애플리케이션에 적합하다.

profile
It's me, jkeum!

0개의 댓글