본문 바로가기
토이프로젝트/페이징 처리

#1. OFFSET 기반 페이징 처리 이해하기

by 거북이의 기술블로그 2025. 2. 11.

페이징(Paging) 이란?

페이징은 데이터가 많을 때, 한 번에 모든 정보를 가져와서 사용자에게 보여주는 대신 일정 개수를 여러 페이지로 나누어 조회하고 화면에 표시하는 기법
  • 장점
    • 성능 향상 : DB나 서버로부터 모든 데이터를 한 번에 불러오는 부담을 줄임
    • 빠른 응답 : 필요한 데이터만 가져오기 때문에 응답시간 단축
    • UI/UX 개선 : 화면에 한꺼번에 많은 정보를 보여주지 않기에 가독성 향상

 

페이징(Paging) 처리 시 고려해야할 점

  • 전체 데이터 개수 (totalCnt)
    • 전체 페이지 수 계산에 사용
    • 검색 조건에 따라 전체 데이터 개수가 달라질 수 있음
  • 현재 페이지 (currentPage)
    • 사용자가 보고자하는 페이지 번호
  • 한 페이지당 데이터 개수 (perPageCnt)
    • 한 페이지에 보여줄 데이터 양
* 시작 위치 : (현재 페이지 - 1 ) * 한 페이지당 개수
(현재페이지에서 "-1"하는 이유는, 첫번째 페이지로 설명하면 "0~perPageCnt"까지 조회되어야하므로 -1을 해주어야한다.)
ex) LIMIT  {한 페이지당 개수} OFFSET {시작위치}

 

[페이지 전체 개수 조회 (totCnt)]

페이징 처리를 위해서, 전체 페이지의 개수를 알려면 COUNT로 전체 데이터 개수를 조회해야한다.
COUNT 함수의 경우 전체 데이터 조회이므로 FULL SCAN을 하게 되는데, 이게 매 페이지 요청마다 totCnt를 호출하게 되면, 페이징 처리로 인한 성능상 이점이 줄어들게 된다. 이 부분을 해결하기 위해서 방법을 생각해보았다.

[ 방법 ]
1. 같은 검색 조건일 경우, 첫 조회시 사용한 TotCnt 재사용
2. 주기적인 갱신이 일어나는 데이터를 조회할경우 구간별로 (ex 10페이지마다) totCnt를 구하여 갱신
3. DB가 제공해주는 통계정보 (ex information_schema 정보)를 통해 근사치로 totCnt를 조회하여 사용
4. 자주 조회되는 조건의 totCnt의 경우 캐싱 (Redis, Caffeine .. ) 하여, totCnt를 쿼리에서 가져오는 것이 아닌 애플리케이션에서 가져와서 빠르게 응답

 

OFFSET의 특징

[ OFFSET의 장점 ]

- 원하는 페이지로 바로 접근 가능
(ex 현재 1페이지를 보고 있다가, 사용자가 12페이지로 한 번에 이동하고 싶으면 OFFSET = (12 - 1) * pageSize 방식으로 바로 쿼리를 호출 가능)

- 구현이 직관적이고 단순 LIMIT n OFFSET x 형태로 손쉽게 쿼리를 제어할 수 있으므로, 단순한 페이지바(Pagination) UI나, “페이지=0부터 시작, size=10”과 같이 page와 size만 가지고 페이징 처리를 구현 (간단)
( 커서 기반 페이징 : 별도의 커서 값(최종 ID)을 관리 필요  )
[ OFSSET의 비효율성 ]

1. 큰 OFFSET 값일수록 성능 저하

(ex 1,000,000 부터 PageSize만큼 조회)
예를 들어 OFFSET 1,000,000 ROWS를 하면 DB는 실제로 1,000,000개의 행을 전부 읽고(또는 스캔/조회) 스킵한 후, 그 다음의 n개만 돌려준다. 즉, 레코드는 ‘버린다’는 개념이지만, DB 입장에서는 스캔 과정에서 I/O와 CPU 자원을 이미 소모해야만 합니다. 결과적으로 페이지 번호(OFFSET)가 커질수록(즉 뒤 페이지로 갈수록) 쿼리 시간이 기하급수적으로 증가

2. 특정 인덱스를 효율적으로 타기 어려운 경우가 많음

OFFSET 기반 페이징은 '상대적인 위치(몇 번째부터)'를 기준으로 잘라내므로, 어떤 인덱스를 이용해도 어차피 OFFSET 이전 레코드를 모두 무시하는 과정이 필요
(무시하는 과정이 존재하기에, 랜덤 페이지 접근이 가능)

반면 "커서 기반 페이징(= Keyset 페이징)"은 '특정 컬럼 값(예: id)이 어떤 값보다 큰/작은' 식으로 조건을 걸기 때문에 인덱스를 타면서 바로 원하는 지점부터 탐색을 이어갈 수 있어 훨씬 효율적
(단, 커서기반페이징은 직전 위치를 기준으로 다음값을 가져오는 구조이므로, 랜덤 페이지 접근이 불가능)

3. 유저 경험(UX) 관점

큰 OFFSET에 도달할 경우 쿼리 응답이 매우 느려지는 상황은, 사용자가 뒤 페이지를 보려고 할 때 대기 시간이 길어지는 문제가 됩니다. 무한 스크롤을 구현하거나, 뒤로 갈수록 성능 문제가 심해지는 상황에선 커서 기반 페이징이 더 적합합니다.

 

SQL 쿼리

SELECT 
*
FROM member
ORDER BY id DESC
LIMIT #{pagePerCnt} OFFSET #{(currentPage - 1)* pagePerCnt};