C.W.K.
Stream
Lesson 05 of 10 · published

Pagination: OFFSET vs Keyset

~12 min · operations, pagination

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

느린 페이지 문제

OFFSET 기반 페이지네이션이 첫 10 페이지엔 OK. 1000 페이지 되면 매 요청에 1만 행 스캔 + 버려달라 DB 에 요청. Latency 가 offset 따라 선형 증가. 결국 유저가 페이지 100 클릭하면 8 초.

Keyset 페이지네이션 — 일정 비용

'페이지 N' 대신 마지막 (sort_value, id) 본 거 기억 + '이거 후 다음 20' 요청. 쿼리가 인덱스 range scan 사용: WHERE (created_at, id) < (last_seen_created_at, last_seen_id) ORDER BY created_at DESC, id DESC LIMIT 20. 페이지 깊이 무관 일정 비용.

Trade-off

Keyset 페이지네이션이 default 로 forward-only — '페이지 50' 점프 없음. 두 커서로 양방향 지원 가능. URL 이 opaque (커서 string, ?page=N 아님). 대부분 'infinite scroll' UI 엔 OK — 페이지 번호가 OFFSET 의 UX 양보, 유저 필요 아님.

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 반환.
Keyset — 일정 비용·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;
복합 커서 위한 row constructor·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 기반 페이지네이션 엔드포인트를 keyset 으로 변환. 페이지 1 과 페이지 100 의 쿼리 latency 전후 비교. 개선이 극적이어야.

Progress

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

댓글 0

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

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