목록 화면을 만들다 보면 pagination은 거의 필수로 들어갑니다. 그런데 처음에는 잘 되던 쿼리가 페이지가 뒤로 갈수록 점점 느려지는 경우가 자주 있습니다. 특히 MySQL에서는 OFFSET 기반 페이지네이션이 커질수록 성능 문제가 드러나는 일이 많습니다.
이 글에서는 아래 내용을 정리합니다.
- pagination 쿼리가 왜 느려지는지
LIMIT ... OFFSET ...방식의 한계- 어떤 경우에 더 나은 방식이 필요한지
- 실무에서 어떤 기준으로 선택하면 좋은지
핵심은 페이지네이션은 단순 UI 문제가 아니라, MySQL이 얼마나 많은 row를 건너뛰고 읽어야 하는지와 직접 연결된 성능 문제라는 점입니다.
왜 뒤 페이지가 느려질까
예를 들어:
SELECT id, title, created_at
FROM posts
ORDER BY created_at DESC
LIMIT 20 OFFSET 10000;
같은 쿼리는 결국 앞쪽 row들을 많이 건너뛴 뒤 필요한 일부를 가져옵니다. 즉, 뒤 페이지로 갈수록 읽고 버리는 양이 계속 커질 수 있습니다.
OFFSET 방식의 한계
OFFSET 방식은 구현이 단순해서 초반에는 매우 편합니다. 하지만:
- 페이지가 깊어질수록 느려질 수 있고
- 정렬 기준이 흔들리면 중복/누락처럼 보일 수 있으며
- 큰 트래픽에서 비효율이 커질 수 있습니다
즉, 단순하고 익숙하다는 장점은 있지만, 항상 확장성이 좋은 방식은 아닙니다.
어떤 대안이 있을까
대표적으로는 cursor 기반 접근이 자주 언급됩니다.
예를 들어:
- 마지막으로 본
created_at,id이후 데이터를 가져오기
같은 방식입니다.
이 방식은 “몇 페이지째인가”보다는 “어디까지 읽었는가”를 기준으로 이어 읽는 구조에 가깝습니다.
cursor 방식이 왜 유리할 수 있을까
cursor 기반 접근은 깊은 페이지로 갈수록 건너뛰는 비용이 커지는 문제를 줄이기 쉽습니다. 특히 정렬 기준이 분명한 목록에서는 훨씬 안정적인 경우가 많습니다.
다만:
- 페이지 번호 UI와 바로 맞추기 어려울 수 있고
- 정렬 기준 설계가 중요하며
- 구현이 조금 더 복잡할 수 있습니다
즉, 성능과 사용성 사이의 선택이 함께 들어갑니다.
어떤 기준으로 선택하면 좋을까
OFFSET이 잘 맞는 경우
- 데이터 양이 크지 않음
- 뒤 페이지 접근이 많지 않음
- 단순한 페이지 번호 UX가 중요함
cursor가 잘 맞는 경우
- 데이터가 많음
- 뒤로 깊게 탐색하는 경우가 많음
- 무한 스크롤이나 “더 보기” UX와 잘 맞음
인덱스는 왜 중요할까
페이지네이션도 결국 정렬과 범위 조회이기 때문에 인덱스가 중요합니다. 특히 정렬 기준 컬럼과 조건 컬럼이 인덱스와 잘 맞지 않으면 성능이 더 나빠질 수 있습니다.
즉, pagination 문제는 SQL 구조만이 아니라 정렬 인덱스 설계와도 연결됩니다.
자주 하는 오해
1. LIMIT이 있으니 항상 빠르다
뒤쪽 OFFSET이 크면 앞부분을 많이 건너뛰는 비용이 여전히 큽니다.
2. 페이지네이션은 프론트 문제다
실제로는 DB 접근 방식과 쿼리 비용 문제입니다.
3. cursor 방식은 무조건 더 좋다
성능에는 유리할 수 있지만, 페이지 번호 기반 UX에는 불편할 수 있습니다.
FAQ
Q. 페이지 번호 UI가 꼭 필요하면 cursor는 못 쓰나
완전히 못 쓰는 것은 아니지만, 설계가 더 복잡해질 수 있습니다.
Q. 뒤 페이지가 느리면 무엇부터 봐야 하나
OFFSET 크기, 정렬 기준, 인덱스 설계를 먼저 보는 것이 좋습니다.
Q. 작은 서비스도 이 문제를 신경 써야 하나
초반에는 단순하게 가도 되지만, 목록 데이터가 빨리 커지는 서비스라면 미리 감을 잡아두는 것이 좋습니다.
Read Next
- 정렬과 접근 경로 설계는 MySQL index design 가이드와 잘 이어집니다.
- 실행 계획으로 실제 읽는 양을 확인하려면 MySQL EXPLAIN 가이드를 같이 보면 좋습니다.
심사 대기 중에는 광고 대신 관련 가이드를 먼저 보여줍니다.
먼저 읽어볼 가이드
검색 유입이 많은 핵심 글부터 이어서 보세요.
- 미들웨어 트러블슈팅 가이드: Redis vs RabbitMQ vs Kafka 개발자를 위한 미들웨어 트러블슈팅 허브 글입니다. Redis, RabbitMQ, Kafka 중 어떤 증상부터 먼저 봐야 하는지와 어떤 문제 패턴이 각 시스템에 가까운지 정리합니다.
- Kubernetes CrashLoopBackOff: 먼저 볼 것들 startup failure, probe, config, resource limit 관점에서 CrashLoopBackOff를 어떻게 나눠서 봐야 하는지 정리한 가이드입니다.
- Kafka consumer lag가 계속 늘 때: 트러블슈팅 가이드 Kafka consumer lag가 계속 늘어날 때 무엇부터 봐야 하는지 정리합니다. poll 주기, 처리 속도, rebalance, consumer 설정까지 실전 기준으로 다룹니다.
- Kafka Rebalancing Too Often 가이드 Kafka consumer group에서 rebalance가 너무 자주 일어날 때 membership flapping, poll timing, protocol, assignment churn을 어떤 순서로 봐야 하는지 설명하는 실전 가이드입니다.
- Docker container가 계속 재시작될 때: 먼저 확인할 것들 exit code, command failure, environment mistake, health check 관점에서 Docker restart loop를 푸는 실전 가이드입니다.
심사 대기 중에는 광고 대신 관련 가이드를 먼저 보여줍니다.