안녕하세요, 백엔드 개발자입니다.
게시판, 상품 목록, SNS 피드 등 많은 서비스에서 페이지네이션(Pagination) 기능은 필수적입니다. 이때 가장 흔히 사용되는 방법은 LIMIT과 OFFSET 키워드를 활용하는 것입니다.
-- 3번째 페이지의 게시글 10개를 조회
SELECT *
FROM posts
ORDER BY id DESC
LIMIT 10 OFFSET 20; -- 앞의 20개를 건너뛰고, 10개를 조회
이 방식은 코드가 직관적이고 "N번째 페이지로 바로 이동" 같은 기능을 구현하기 편리하다는 장점이 있습니다.
하지만 서비스 규모가 커지고 데이터가 쌓이면, 이 편리함이 성능 저하의 원인이 되기도 합니다. 이번 글에서는 OFFSET을 사용한 페이지네이션이 어떤 방식으로 동작하고, 왜 성능 이슈를 유발할 수 있는지 그 원리를 알아보겠습니다.
1. OFFSET의 동작 원리
OFFSET 20이라는 쿼리를 보면, 데이터베이스가 앞선 20개의 데이터를 효율적으로 건너뛴 후 21번째 데이터부터 읽을 것이라고 기대하기 쉽습니다. 하지만 실제 동작 방식은 조금 다릅니다.
데이터베이스는 OFFSET 20, LIMIT 10 쿼리를 다음과 같이 처리합니다.
- ORDER BY절을 기준으로 데이터를 정렬합니다.
- 처음부터 20개 + 10개 = 총 30개의 데이터를 조회합니다.
- 조회된 30개의 데이터 중, 앞의 20개를 버립니다.
- 나머지 10개를 결과로 반환합니다.
OFFSET 값이 작을 때는 큰 문제가 되지 않습니다. 하지만 페이지 번호가 뒤로 갈수록, 예를 들어 OFFSET 1000000과 같은 쿼리가 실행된다면 어떻게 될까요? 데이터베이스는 단 10개의 데이터를 얻기 위해 100만 10개의 데이터를 읽고, 그 중 100만 개를 메모리에 올렸다가 버리는 비효율적인 작업을 수행하게 됩니다.
결과적으로 반환하는 데이터 수는 10개로 동일하지만, OFFSET 값이 커질수록 데이터를 조회하는 데 드는 내부 비용은 선형적으로 증가하는 것입니다.
2. 실행 계획(Execution Plan)으로 확인하기
데이터베이스가 제공하는 EXPLAIN 명령어를 사용하면 쿼리가 내부적으로 어떻게 실행되는지 확인할 수 있습니다.
-- OFFSET 값이 작을 때
EXPLAIN SELECT * FROM posts ORDER BY id DESC LIMIT 10 OFFSET 20;
-- OFFSET 값이 클 때
EXPLAIN SELECT * FROM posts ORDER BY id DESC LIMIT 10 OFFSET 1000000;
두 쿼리의 실행 계획을 비교해 보면, OFFSET 값이 클수록 스캔하는 데이터의 양(rows)이 크게 증가하는 것을 볼 수 있습니다.
이것이 OFFSET 기반 페이지네이션의 핵심적인 문제입니다. 서비스 초기에는 눈에 띄지 않다가 데이터가 누적될수록 API 응답 속도가 저하되고, 결국 시스템 전체에 부하를 주는 '슬로우 쿼리(Slow Query)'의 원인이 될 수 있습니다.
3. 정리하며
이번 편에서는 LIMIT과 OFFSET을 사용한 페이지네이션의 동작 방식과 그로 인한 성능 이슈에 대해 알아보았습니다.
- OFFSET은 N개를 건너뛰는 것이 아닌, (N + LIMIT)개를 읽고 N개를 버리는 방식으로 동작합니다.
- 페이지 번호가 뒤로 갈수록(OFFSET 값이 커질수록) 쿼리 성능은 저하될 수 있습니다.
- 이는 데이터가 많은 서비스에서 심각한 성능 병목의 원인이 될 수 있습니다.
그렇다면 이 문제를 해결할 대안은 무엇일까요? 다음 편에서는 OFFSET을 사용하지 않고, 인덱스를 통해 일관된 성능을 유지하는 '커서 기반 페이지네이션(Cursor-based Pagination)'에 대해 알아보겠습니다.
긴 글 읽어주셔서 감사합니다.
'Backend' 카테고리의 다른 글
| COUNT(*) 대신 EXISTS 써보기 (0) | 2025.09.08 |
|---|---|
| OFFSET 페이지네이션 개선하기: 커서 기반 페이지네이션 도입 (0) | 2025.09.02 |
| 나만의 데코레이터 만들기: 실전 기능 구현 (0) | 2025.08.29 |
| 나만의 데코레이터 만들기: NestJS에서 반복적인 API 설정 줄이기 (0) | 2025.08.27 |
| 서버 부하를 줄이는 이미지 업로드 전략 (3편): 유연한 썸네일 URL 관리 패턴 (0) | 2025.08.24 |
