3줄 요약
- 목록 API는 단순 조회처럼 보이지만 서비스가 커질수록 가장 먼저 느려지는 영역 중 하나다.
- Offset pagination은 구현이 쉽지만 뒤 페이지로 갈수록 비용이 커지고, cursor/keyset pagination은 안정적인 정렬 기준과 인덱스가 필요하다.
- 검색과 정렬은 인덱스, 데이터 분포, 사용자 경험, 일관성 요구사항을 함께 설계해야 한다.
핵심 정리
LIMIT 20 OFFSET 100000은 100000개를 건너뛰는 비용을 DB가 감당해야 할 수 있다. 뒤 페이지 이동이 많은 API에서는 위험하다.
- Cursor pagination은 마지막으로 본 정렬 키를 기준으로 다음 페이지를 가져온다.
created_at, id처럼 안정적이고 유일한 정렬 조합이 필요하다.
- 정렬은 인덱스 설계와 붙어 있다.
WHERE status = ? ORDER BY created_at DESC, id DESC LIMIT 20은 (status, created_at DESC, id DESC) 계열 인덱스로 좋아질 수 있다.
- 검색은
LIKE '%keyword%'로 시작하면 일반 B-Tree 인덱스를 활용하기 어렵다. prefix 검색, full-text search, trigram, 외부 검색 엔진을 구분해야 한다.
- 무한 스크롤은 중복과 누락을 API 계약으로 다뤄야 한다. 새 데이터가 중간에 끼어들면 offset 방식에서 페이지가 흔들릴 수 있고, cursor 방식도 정렬 키와 필터 검증이 틀리면 같은 문제가 난다.
- 개인 프로젝트에서도 cursor pagination을 한두 API에 제대로 적용해보면 인덱스와 API 계약 이해도가 크게 올라간다.
- Spring Data
Page는 total count 쿼리를 동반할 수 있다. 본문 조회는 빠른데 count가 느린 경우가 흔하므로, 전체 페이지 수가 필요 없으면 Slice나 cursor 응답을 검토한다.
- Cursor token은 단순 파라미터가 아니라 API 계약이다. 정렬 키, 방향, tie-breaker, token 버전, 위변조 방지, 만료 정책을 생각해야 한다.
헷갈리는 지점
- pagination은
Pageable을 쓰면 끝이라고 생각하기 쉽다. 프레임워크가 limit/offset을 만들어주기 때문이다.
- 핵심은 프레임워크가 비용 문제를 없애주지는 않는다는 점이다.
- 실제 SQL, count 쿼리, 정렬 인덱스, 뒤 페이지 비용을 확인해야 한다.
- cursor는 항상 offset보다 좋다고 오해하기 쉽다. 대용량 목록에서 cursor가 자주 추천되기 때문이다.
- 핵심은 cursor가 임의 페이지 이동과 총 페이지 수 UI에는 불리하다는 점이다.
- 사용자 경험과 데이터 규모에 맞게 선택한다.
- 검색은 DB
LIKE로 충분하다고 생각하기 쉽다. 초기 데이터에서는 빠르게 동작하기 때문이다.
- 핵심은 검색 요구사항이 부분 일치, 형태소, 랭킹, 오타 허용으로 가면 DB 기본 인덱스만으로 부족할 수 있다는 점이다.
- 요구 수준에 따라 full-text, trigram, Elasticsearch/OpenSearch 같은 전용 검색을 검토한다.
- 관리자 화면도 사용자 API와 같은 성능 기준을 가져야 한다고 생각하기 쉽다. 둘 다 목록 화면이기 때문이다.
- 핵심은 관리자 기능은 임의 페이지 이동과 복잡한 필터가 중요할 수 있고, 사용자 피드는 p99와 무한 스크롤 안정성이 더 중요할 수 있다는 점이다.
- UX 요구, 호출 빈도, 데이터량, SLA를 기준으로 offset, cursor, export, async search를 나눠야 한다.
확인 질문
- Offset pagination이 대용량 뒤 페이지에서 느려지는 이유는 무엇인가?
- DB가 앞의 많은 행을 찾아 건너뛴 뒤 필요한 개수만 반환해야 할 수 있기 때문이다.
- Cursor pagination에 필요한 정렬 조건은 무엇인가?
- 결과 순서가 안정적이고 중복이 없도록
created_at, id처럼 유일하게 순서를 결정할 수 있는 컬럼 조합이 필요하다.
- 검색 기능을 설계할 때 먼저 정해야 하는 것은 무엇인가?
- prefix 검색인지 부분 검색인지, 정렬 기준이 최신순인지 관련도순인지, 데이터 규모와 지연 허용 범위가 어느 정도인지 정해야 한다.
Page 대신 Slice나 cursor를 검토해야 하는 신호는 무엇인가?
- 전체 페이지 수가 필요 없고, count 쿼리가 느리거나, 뒤 페이지 이동보다 다음 목록 조회가 중요하며, 데이터 삽입이 계속 일어나는 목록일 때다.