본문 바로가기
C.W.K.
Stream
Lesson 05 of 10 · published

OFFSET과 키셋 페이지 나누기

~12 min · operations, pagination

Level 0스키마 새싹
0 XP0/86 lessons0/10 achievements
0/120 XP to next level120 XP to go0% complete

OFFSET은 앞의 행도 읽고 버려

처음 10페이지는 괜찮아 보여도 ?page=N의 N이 1000이면 요청할 때마다 앞의 1만 행을 읽고 버려. OFFSET 비용은 선형으로 늘어서 사용자가 100페이지를 눌렀을 때도 8초가 걸릴 수 있어.

마지막 위치에서 이어 읽어

마지막으로 본 (created_at, id)를 커서로 보내고 그보다 뒤의 20행을 ORDER BY와 LIMIT으로 가져오면 색인 범위가 곧장 이어져. 페이지 50이든 더 깊든 20행씩 읽는 비용은 일정해.

항상 동점 기준을 넣어

같은 시각 값이 있을 수 있으므로 id를 함께 정렬해 중복과 누락을 막아. 키셋 방식은 기본적으로 앞쪽 탐색만 쉽고 임의의 ‘50페이지’ 점프는 포기해야 해. 이전·다음 커서를 따로 두면 양방향 이동은 가능하지만 URL은 ?page=N 대신 불투명한 커서 값을 쓰게 돼.

Code

깊은 페이지에서 느려지는 OFFSET·sql
-- 페이지 1
SELECT * FROM articles ORDER BY created_at DESC LIMIT 20;

-- 페이지 100
SELECT * FROM articles ORDER BY created_at DESC LIMIT 20 OFFSET 1980;
-- 2000 행 read, 20 반환.
일정한 비용의 키셋 페이지네이션·sql
-- 첫 페이지
SELECT id, title, created_at
FROM   articles
ORDER  BY created_at DESC, id DESC
LIMIT  20;

-- 다음 페이지: 마지막 (created_at, id) 를 커서로
SELECT id, title, created_at
FROM   articles
WHERE  (created_at, id) < ('2026-04-01 00:00:00', 12345)
ORDER  BY created_at DESC, id DESC
LIMIT  20;
복합 커서를 위한 행 생성자·python
# 이전 페이지의 마지막 본 값
last_created_at = "2026-04-01 00:00:00"
last_id = 12345

rows = db.execute(
    """
    SELECT id, title, created_at
    FROM   articles
    WHERE  (created_at, id) < (%s, %s)
    ORDER  BY created_at DESC, id DESC
    LIMIT  %s
    """,
    (last_created_at, last_id, 20)
).fetchall()

External links

Exercise

OFFSET 기반 페이지네이션 엔드포인트를 키셋 방식으로 바꿔. 1페이지와 100페이지의 질의 지연 시간을 전후로 비교해 깊은 페이지에서 개선 폭이 큰지 확인해.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.