게시판 목록을 OFFSET/LIMIT로 페이징하고 있는데, 1페이지는 즉시 나오는데 3000페이지쯤(OFFSET 60000) 가면 응답이 몇 초씩 걸립니다. created_at에 인덱스도 걸려 있어요.
SELECT id, title, created_at
FROM posts
ORDER BY created_at DESC
OFFSET 60000 LIMIT 20;이상한 게, 앞쪽 페이지는 같은 쿼리인데도 빠르거든요. 인덱스가 있으면 뒤 페이지도 바로 20개만 집어오면 되는 거 아닌가 싶은데, 왜 페이지가 깊어질수록 점점 느려지는 걸까요? 데이터는 한 30만 건쯤 됩니다.
댓글 2
해결은 keyset 페이지네이션(커서 방식)으로 바꾸는 겁니다. 마지막으로 본 행의 정렬 키를 기억했다가 그 다음부터 집는 방식이에요.
SELECT id, title, created_at FROM posts
WHERE created_at < :lastCreatedAt
ORDER BY created_at DESC LIMIT 20;
이러면 인덱스로 시작 지점을 바로 찾아 들어가서, 몇 페이지든 딱 20건만 읽습니다. 페이지 깊이랑 상관없이 일정하게 빨라요.
한 가지 주의는 created_at이 겹치는 행이 있으면 경계에서 건너뛰거나 중복이 생길 수 있어서, (created_at, id) 복합으로 타이브레이커를 두는 게 안전합니다. WHERE (created_at, id) < (:lastCreatedAt, :lastId) 식으로요. 인덱스도 (created_at DESC, id DESC)로 맞춰두면 좋고요.
단점은 '임의 페이지 번호로 점프'가 안 된다는 건데, 무한 스크롤이나 다음/이전 방식이면 keyset이 정답입니다. 페이지 번호가 꼭 필요하면 깊은 페이지를 제한하거나 별도로 처리하세요.