offset 페이지네이션이 느려지는 이유와 커서 전환기
목록 API에서 offset 페이지네이션이 깊은 페이지일수록 느려지는 이유와, 커서(keyset) 방식으로 바꾸며 얻은 것과 포기한 것을 정리했어요.
offset 페이지네이션이 느려지는 이유와 커서 전환기
목록 API를 만들 때 대부분 LIMIT 20 OFFSET N으로 시작해요. 저도 그랬고요. 처음엔 잘 돌아가는데, 데이터가 쌓이고 뒤쪽 페이지로 갈수록 응답이 눈에 띄게 느려지더라고요. 혹시 목록 조회가 페이지 번호 커질수록 버벅인 적 없나요?
OFFSET은 건너뛴 만큼 다 읽어요
OFFSET 10000은 앞의 1만 건을 그냥 버리려고 끝까지 훑는다는 뜻이에요. 필요한 건 뒤의 20건뿐인데, DB는 1만 20건을 읽고 앞 1만 건을 던져버려요. 직접 테스트 데이터를 넣고 500번째 페이지를 돌려 보니, 첫 페이지와 실행 계획 자체가 달라지는 게 보였어요.
더 골치 아픈 건 흔들림이에요. 사용자가 1페이지를 보는 사이 새 글이 하나 올라오면, 2페이지에서 방금 본 항목이 또 나와요. 목록이 실시간으로 바뀌는 서비스라면 중복과 누락이 조용히 쌓여요.
offset 방식과 커서(keyset) 방식의 조회 특성 비교
커서(keyset)는 마지막 값을 기준점으로
커서 방식은 페이지 번호 대신 "마지막으로 본 행"을 기준으로 다음을 가져와요. 정렬 기준이 created_at DESC라면 다음 페이지 조건은 이렇게 돼요.
SELECT * FROM posts
WHERE (created_at, id) < (:last_created_at, :last_id)
ORDER BY created_at DESC, id DESC
LIMIT 20;created_at이 겹칠 수 있어서 id를 같이 묶어 동점을 끊어줘요. 인덱스 컬럼 2개로 복합 조건을 걸면, DB는 인덱스를 타고 딱 20건만 집어와요. 커서 방식으로 바꿔 봤더니 500페이지든 1페이지든 읽는 양이 같아지고, 로그를 찍어 보니 깊은 페이지에서 스캔 행 수가 확 줄어 있었어요.
- 정렬키+고유키 복합 커서
- 인덱스 정합
- 다음/이전만 지원
대신 포기하는 것
공짜는 아니에요. 커서는 "몇 페이지로 점프"가 안 돼요. 다음과 이전 이동만 자연스럽고, 임의 페이지 번호가 필요한 화면(관리자 페이징 등)엔 안 맞아요. 그래서 저는 무한 스크롤이나 피드형 목록은 커서로, 페이지 번호가 꼭 필요한 관리 화면은 offset으로 나눠 써요.
법령·판례 검색처럼 결과가 계속 늘어나는 목록에도 커서가 잘 맞아요. 플랜엘(plan-l)에서 월 3건 무료로 검색을 돌려보면, 결과를 아래로 이어 보는 흐름이 페이지 점프보다 훨씬 자연스럽거든요.
정리하면 판단 기준은 하나예요. 임의 페이지 점프가 필요하면 offset, 앞으로만 흐르는 목록이면 커서. 목록이 무거워지기 전에 미리 갈라두면 나중에 인덱스만 손보는 고생을 줄일 수 있어요.
관련 글
댓글
아직 댓글이 없어요. 첫 댓글을 남겨주세요.