오답 기록 페이지 누락 해결과 복합 커서 적용

이 글의 목차 7개 항목
  • 분야: DB
  • 기술 스택: TypeScript · Cloudflare D1 · SQL

문제와 원인

오답 목록은 created_at DESC, id DESC로 정렬했지만 다음 페이지는 id < 마지막 ID만 검사했다. 첫 페이지의 801보다 과거에 생성된 803·802·804가 모두 ID 조건에서 빠져 두 번째 페이지가 비었다.

정렬 순서기록 ID기록 시각 UTC
18012026-01-04T00:00:00Z
28032026-01-03T00:00:00Z
38022026-01-03T00:00:00Z
48042026-01-01T00:00:00Z

수정 전 코드

readLatestIncorrectAttempts

AND id < ?

수정 후 코드

readLatestIncorrectAttempts

커서보다 과거인 기록을 포함하고, 같은 시각이면 더 작은 ID를 포함하도록 정렬과 비교 조건을 맞췄다. 마지막 기록의 시각을 두 번, ID를 한 번 바인딩한다.

AND (created_at < ? OR (created_at = ? AND id < ?))
ORDER BY created_at DESC, id DESC

encodeAttemptCursor

커서는 마지막 기록의 [기록 시각, ID]로 인코딩한다.

export function encodeAttemptCursor(row: AttemptCursor) {
  return JSON.stringify([row.createdAt, row.id]);
}

이전 숫자 커서는 해당 사용자·시험의 기록에서 시각을 조회해 복합 커서로 변환한다. 해석할 수 없는 커서는 HTTP 400으로 거부한다.

검증 결과

Wrangler의 로컬 D1에 합성 본인 오답 4건과 다른 사용자의 ID 899 기록 1건을 넣었다. 원본 전후 readLatestIncorrectAttempts를 페이지 크기 1로 실행하고 각 페이지의 마지막 기록을 다음 커서로 전달했다.

비교 항목수정 전수정 후
첫 페이지[801][801]
후속 페이지두 번째 페이지 []에서 종료[803] → [802] → [804]
같은 시각의 803·802두 기록 누락803 다음 802 반환
과거 시각의 큰 ID 804누락마지막 페이지에 반환
대상 4건 중 반환1/4건4/4건
누락3건0건
중복·다른 사용자 기록각각 0건각각 0건

같은 입력 행의 SQL 비교에서 before_cursor·after_cursor 값 1은 첫 기록 다음에 포함되는 행을 뜻한다. 기존 조건은 나머지 3건을 제외했고 복합 조건은 모두 포함했다.

D1 SQL: 복합 조건으로 다음 오답 3건 포함원본 보기 (새 창)

D1 SQL: 복합 조건으로 다음 오답 3건 포함

100%원본 보기 (새 창)

확대한 이미지는 상하좌우로 이동할 수 있습니다. 키보드는 이미지 영역에 초점을 두고 방향키를 사용하세요. Esc로 닫습니다.