← 문제로

24. 유일하지 않은 정렬 키의 키셋 페이지네이션: 경계 행 누락

난이도 상
내 리뷰 · C#
해설 · C#

해설 — 유일하지 않은 정렬 키의 키셋 페이지네이션: 경계 행 누락

난이도: 상

요약

정렬·seek 키가 level(비유일) 하나뿐이라, 같은 레벨의 행들이 페이지 경계에 걸치면 다음 페이지가 level < @lvl (A) 로 경계 레벨을 통째로 건너뛴다. 커서를 rows[last].Level (B) 로만 잡기 때문에 경계 레벨의 남은 행들이 영구히 누락된다.

문제점

  • [정합성] 경계에서 동일 키 행 누락: pageSize=3, 레벨이 [50, 50, 50, 50, 40, ...] 라 하자.
    • 1페이지: 앞의 50 세 개를 반환, 커서 next=50.
    • 2페이지: WHERE level < 50 → 남은 네 번째 50 이 조건에서 제외되어 영영 안 나온다. 40 부터 시작. → 같은 레벨이 페이지 크기 경계에 걸친 만큼의 행이 통째로 사라진다.
  • [대안 오류로도 실패] <= 로 바꾸면 중복: 누락을 피하려 level <= 50 으로 바꾸면 1페이지의 50 세 개가 2페이지에 다시 나온다(무한 재조회 위험). 즉 유일 키 없이 </<= 어느 쪽도 정답이 아니다 — 근본 원인은 타이브레이커 부재다.
  • [근본 원인] 전순서(total order) 부재: 키셋 페이지네이션은 정렬 키가 행마다 유일해야 경계가 명확히 잘린다. level 만으로는 동률 행의 순서가 정의되지 않아 경계가 모호해진다.
  • 재현 조건: 동일 레벨(또는 동일 점수/시각) 행이 pageSize 배수 경계에 걸칠 때. 대규모 길드·랭킹에서 흔함.

수정안

유일한 타이브레이커(예: member_id)를 정렬과 seek 조건에 함께 넣어 전순서를 만든다. 커서도 (레벨, member_id) 복합으로 들고 다닌다.

-- 첫 페이지
SELECT member_id, level FROM guild_members
WHERE guild_id = @g
ORDER BY level DESC, member_id ASC
LIMIT @n;

-- 다음 페이지: (level, member_id) 복합 seek
SELECT member_id, level FROM guild_members
WHERE guild_id = @g
  AND ( level < @lastLevel
        OR (level = @lastLevel AND member_id > @lastMemberId) )
ORDER BY level DESC, member_id ASC
LIMIT @n;
// 커서를 (Level, MemberId) 로 확장
public readonly struct Cursor { public readonly int Level; public readonly long MemberId; /* ... */ }

int? nextLevel; long? nextId;
if (rows.Count == pageSize)
{
    var last = rows[rows.Count - 1];
    nextLevel = last.Level; nextId = last.MemberId;   // 복합 커서
}

(level, member_id) 복합 인덱스가 있어야 이 seek 가 인덱스만으로 효율적으로 처리된다.

더 나은 설계

  • 정렬 키에 항상 유일 타이브레이커 포함: 마지막 컬럼으로 PK 를 붙여 전순서를 보장한다(랭킹·목록 공통 규칙).
  • 복합 인덱스 정렬 방향 일치: ORDER BY level DESC, member_id ASC 와 seek 조건이 인덱스 순서와 맞아야 인덱스 스캔으로 끝난다.
  • OFFSET 회피 이유 재확인: OFFSET 은 동시 삽입/삭제 시 스큐로 누락·중복이 나고 뒤 페이지가 느리다. 키셋은 이를 피하지만 "유일 키" 전제를 반드시 지켜야 한다.
  • 트레이드오프: 복합 커서는 조건이 복잡해지지만 정확성·성능을 모두 얻는다. 튜플 비교 (level, member_id) < (@l, @id) 를 지원하는 DB 면 더 간결하게 쓸 수 있다.

면접 포인트

  • 키셋 페이지네이션이 "정렬 키 유일성" 을 요구하는 이유와, 비유일 키에서 </<= 가 각각 누락·중복이 되는 원리.
  • 타이브레이커(PK) 추가와 복합 인덱스 설계.
  • OFFSET vs 키셋의 스큐·성능 트레이드오프.