받은 개수가 전체 개수와 같아도 누락이 없다고 말할 수 없습니다
API에서 한 번에 전체 자료를 주지 않으면 여러 페이지를 이어 읽어야 합니다. 각 요청이 성공하고 받은 행 수도 예상과 맞으면 작업이 끝난 것처럼 보입니다. 그러나 페이지 사이에 자료가 추가되거나 수정되면 같은 ID를 다시 받고 다른 ID를 놓칠 수 있습니다. 중복 한 개와 누락 한 개가 개수에서 서로 상쇄되면 총 행 수만 보는 검사는 통과합니다. 따라서 수집 완료를 판단하려면 요청의 종료 조건과 관찰한 ID, 버전 충돌을 나눠 기록해야 합니다.
이번 실험은 외부 API를 호출하지 않고 정렬된 가상 목록을 메모리에서 바꾸며 페이지 응답을 만들었습니다. 첫 페이지를 읽은 뒤 새 항목을 앞에 넣고, 이미 받은 항목의 버전도 바꿨습니다. 이후 같은 OFFSET 규칙으로 읽었을 때 받은 행과 실제 기준 목록을 대조했습니다. 별도로 고정된 초기 목록을 토큰 체인으로 읽는 사례와, 빈 페이지·반복 토큰의 경계도 실행했습니다. 특정 API 업체의 실제 일관성이나 성능을 평가한 기록은 아닙니다.
실제 업무에서도 먼저 무엇의 전체를 모으는지 정의해야 합니다. 작업 시작 시점의 목록인지, 종료 시점의 최신 목록인지, 특정 기간에 발생한 변경의 목록인지에 따라 완료 기준이 달라집니다. API가 스냅샷을 약속하지 않는데 시작 시점과 종료 시점의 개수를 섞으면 검사 기준 자체가 흔들릴 수 있습니다. 요청 횟수보다 이 시간 범위를 먼저 적어 두는 편이 좋습니다.
첫 페이지 뒤에 R-099를 넣고 R-102를 수정했습니다
초기 목록은 R-100부터 R-107까지 여덟 항목이며 각 버전은 1입니다. 페이지 크기는 3으로 고정했습니다. 첫 페이지에서는 R-100, R-101, R-102를 받습니다. 그다음 새 항목 R-099를 맨 앞에 넣고 R-102의 버전을 2로 바꿨습니다. 다음 페이지를 현재 목록의 세 번째 위치를 건너뛴 뒤부터 읽자 R-102가 다시 나타났습니다. 첫 페이지에서 읽은 것은 버전 1, 두 번째에서 읽은 것은 버전 2입니다.
| 순서 | 응답 ID | 주목할 점 |
|---|---|---|
| 첫 페이지 | R-100, R-101, R-102 | R-102 버전 1 |
| 목록 변경 | R-099 추가, R-102 수정 | 기준 목록은 9개 |
| 두 번째 페이지 | R-102, R-103, R-104 | R-102 버전 2 재수신 |
| 세 번째 페이지 | R-105, R-106, R-107 | R-099는 받지 못함 |
실제 받은 행은 9개이고 변경 뒤 목록도 9개입니다. 하지만 받은 고유 ID는 8개이며 R-099가 빠졌습니다. 이 사례에서는 실험이 현재 목록의 정답을 직접 갖고 있으므로 누락 ID까지 확인할 수 있었습니다. 실제 서비스의 외부 사용자가 같은 정보를 갖는다는 뜻은 아닙니다. 기준 원장이 없는 경우에는 중복과 충돌을 관찰했다고 말할 수 있어도, 받지 못한 모든 ID를 확정했다고 말하기는 어렵습니다.
정렬을 고정해도 목록의 삽입 위치가 바뀌면 OFFSET이 밀립니다
이 예제의 목록은 ID 순서로 정해져 있습니다. 순서가 전혀 없는 조회를 흉내 낸 것이 아닙니다. 그럼에도 앞쪽에 항목이 추가되자 이미 읽은 R-102가 다음 OFFSET 구간으로 밀렸습니다. 정렬이 있다는 사실과 여러 요청이 같은 자료 상태를 읽는다는 사실은 별개입니다. PostgreSQL 문서는 LIMIT으로 부분 결과를 얻을 때 유일한 순서를 정해야 예측 가능한 부분집합을 얻을 수 있음을 설명합니다. 그 조건을 갖춰도 요청 사이의 변경이라는 추가 문제가 남을 수 있습니다.
실제 API가 page=2나 offset=3을 받는다고 해서 무조건 잘못된 설계라는 뜻은 아닙니다. 변하지 않는 자료나 일관된 스냅샷 위에서는 그 계약으로 충분할 수 있습니다. 확인해야 할 것은 페이지 번호의 이름보다 조회 기간, 정렬 키, 변경 반영 방식과 일관성 설명입니다. 삭제나 정렬 기준 필드의 수정도 위치를 바꿀 수 있으므로, 자신의 사용 환경에서 어떤 변화가 일어나는지 가상 입력으로 나눠 보면 좋습니다.
자료가 계속 바뀌는 목록이라면 제공자가 스냅샷 식별자, 안정된 변경 시각, 단조 증가 키나 별도 변경 로그를 제공하는지 살펴볼 수 있습니다. 이런 기능이 있다는 이름만으로 완전성을 보장하지는 않습니다. 같은 시각의 여러 항목을 어떻게 구분하는지, 경계 포함 규칙과 보관 기간이 무엇인지까지 계약으로 확인해야 합니다. 이 글의 작은 배열 실험은 그 기능들을 실제로 호출해 비교한 결과가 아닙니다.
중복 ID는 지우기 전에 내용 충돌 여부를 남깁니다
수집 원장은 처음 본 ID와 값을 보관하고, 같은 ID가 다시 나타나면 페이지와 내용을 비교합니다. 실험에서는 R-102의 두 버전이 달라 충돌도 함께 기록됐습니다. 받은 행을 ID 기준으로 단순히 한 번만 남기면 이 차이를 놓칠 수 있습니다. 처음 값을 유지하면 최신 변경을 버릴 수 있고, 마지막 값을 유지하면 수집 중 상태가 달라졌다는 사실을 숨길 수 있습니다. 어느 값을 최종 사용해야 하는지는 API의 버전 계약과 업무 목적에 따라 정해야 합니다.
다운로드 결과의 원장은 비교용이며 최신 상태를 승인한 출력물이 아닙니다. 충돌한 두 값을 함께 기록했지만 실제 저장소에 어느 한쪽을 반영하지 않았습니다. 페이지 응답을 원문 그대로 또는 해시와 함께 보관하면 나중에 경계를 다시 조사하는 데 도움이 됩니다. 그때도 저장 정책과 개인정보 범위를 확인해야 합니다. 요청 URL이나 토큰에 민감한 값이 들어간다면 원장에 그대로 공개하지 않도록 별도 관리가 필요합니다.
ID가 비어 있거나 한 페이지 안에서 같은 ID가 여러 번 나오는 사례도 운영 검수에 추가할 수 있습니다. 같은 기록과 충돌하는 기록을 나누는 가이드는 이 판단의 기본을 설명합니다. API 수집에서는 여기에 어떤 페이지와 조회 조건에서 관찰했는지를 더해야 합니다. 같은 ID라는 사실 하나로 페이지 수집의 완료 여부를 판단하지 않습니다.
빈 페이지가 나와도 다음 토큰이 있으면 계속 읽습니다
별도의 토큰 사례는 초기 여덟 항목을 고정된 기준으로 사용했습니다. 첫 응답에 세 항목, 다음 응답에 영 항목, 그다음에 세 항목, 마지막에 두 항목을 넣었습니다. 두 번째 응답은 비었지만 다음 토큰을 갖고 있습니다. 코드는 항목 수가 0이라는 이유로 끝내지 않고 토큰을 따라갔고, 최종적으로 초기 목록의 여덟 ID를 모두 얻었습니다. 이 사례에서는 스냅샷을 코드가 고정했기 때문에 초기 목록과의 완전성을 검사할 수 있었습니다.
Google AIP-158은 페이지 크기보다 적게 주거나 빈 결과를 주어도 다음 페이지가 남을 수 있는 계약을 설명합니다. 그 계약에서 종료는 빈 next_page_token으로 판단합니다. GitHub REST 문서는 응답 Link 헤더의 rel=next 주소를 따라 다음 페이지를 읽는 방식을 안내합니다. 두 문서는 서로 다른 API 형태의 예입니다. 모든 API에 같은 필드명을 넣거나 데이터가 비면 무조건 계속 요청하라는 규칙으로 합쳐서는 안 됩니다. 사용 중인 응답 계약의 종료 표시를 따라야 합니다.
실제 토큰은 내부 구조를 해석하지 않는 불투명 값일 수 있습니다. 실험에서 start나 middle-empty라는 읽기 쉬운 이름을 쓴 것은 흐름을 보여 주기 위한 선택입니다. 실제 토큰을 숫자로 추측해 더하거나 문자열 일부를 바꿔 다음 값을 만드는 방식은 이 예제에서 검증한 동작이 아닙니다. 필터나 정렬 조건을 바꾸었을 때 기존 토큰을 계속 사용할 수 있는지도 제공자의 문서에서 확인하세요.
같은 토큰이 되돌아오면 완료로 표시하지 않고 멈춥니다
반복 토큰 사례에서는 시작 응답이 다시 start를 다음 값으로 돌려줍니다. 코드는 이미 요청한 토큰 집합을 확인해 두 번째 반복 전에 오류로 멈춥니다. 무한 요청을 방지한 것이지 수집이 정상 완료됐다는 뜻은 아닙니다. 정상 종료와 비정상 중단을 같은 완료 상태로 기록하면 마지막 페이지가 없었던 문제를 놓치게 됩니다. 운영 원장에는 종료 이유를 별도 값으로 남기고, 원인이 확인될 때까지 결과의 완전성을 확정하지 않는 편이 좋습니다.
반복 검사만으로 모든 긴 수집을 제어할 수 있는 것은 아닙니다. 매번 다른 토큰을 무한히 주는 경우나 매우 긴 작업에는 최대 페이지 수, 총 시간, 수신 바이트의 한도가 필요합니다. 실험은 유한한 작은 응답표를 사용했으므로 그런 자원 한도를 측정한 코드는 아닙니다. 실제 처리기를 만들 때는 정상적인 대규모 작업의 범위를 문서화하고, 그 범위를 넘으면 조용히 잘라 완료로 표시하지 말고 중단 사유를 남겨야 합니다.
오류 후 재시작할 때는 토큰만 저장하면 되는지도 확인하세요. 토큰이 만료되거나 특정 조회 조건에 묶여 있을 수 있고, 다시 읽은 페이지가 중복될 수도 있습니다. 이전 정상 결과와 새 결과를 합치는 정책, 충돌 기록을 이어 쓰는 방법, 체크포인트를 확정하는 시점을 함께 정해야 재시작 뒤의 개수도 설명할 수 있습니다. 이번 토큰은 로컬 문자열이므로 실제 만료나 네트워크 실패를 시험하지 않았습니다.
검사 항목은 요청 성공·자료 상태·업무 값으로 나눕니다
페이지마다 상태 코드가 성공인지 확인하는 것은 필요하지만 충분하지 않습니다. 응답의 구조가 맞는지, 다음 토큰이나 Link가 계약대로 있는지, 중복과 충돌이 얼마나 생겼는지도 확인해야 합니다. 정상적으로 JSON이 읽혀도 항목 배열이 다른 필드로 이동했거나 ID의 자료형이 바뀌면 원장이 잘못 묶일 수 있습니다. 문법과 데이터 규칙을 나눠 검사하는 가이드는 이 앞단을 설명합니다. 구조가 맞은 뒤에도 값의 사실성과 누락 여부는 별도로 남습니다.
통계표에는 받은 행 수, 고유 ID 수, 중복 관찰 수, 충돌 ID 수, 정상 종료 여부를 나눠 적을 수 있습니다. 제공자가 전체 개수를 주더라도 그 수가 추정인지, 어느 시점의 값인지 확인하세요. 이번 예제는 받은 9행과 현재 기준 9개가 같아도 한 개를 놓쳤습니다. 총계 일치가 검사 항목 하나라는 점을 보여 주는 사례이지, 총계가 항상 쓸모없다는 뜻은 아닙니다. 기준과 시점이 명확하면 여전히 유용한 보조 증거가 됩니다.
필터와 정렬 조건도 원장에 함께 남기면 다른 사람이 같은 범위를 재현하기 쉽습니다. 날짜 범위를 바꾸거나 상태 필터가 달라진 두 작업의 ID 수를 그대로 비교하면 실제 누락과 조건 차이를 혼동할 수 있습니다. 결과 개수보다 먼저 같은 대상을 비교하는지 확인하는 순서를 유지하세요.
새 수집기를 만들 때는 완료 원장에 기준 목록이 실제로 있었는지 명시할 수 있습니다. 기준이 없는 실행을 누락 0건이라고 채우는 대신 미확인으로 남기면, 나중에 제공자의 별도 내보내기와 대조할 때 무엇을 새로 알게 됐는지 구분하기 쉽습니다.
토큰을 쓴다는 이름만으로 스냅샷을 약속하지 않습니다
이번 토큰 사례가 초기 목록을 모두 읽은 것은 데이터를 고정한 가상 응답표 덕분입니다. 커서라는 방식 자체가 모든 서비스에서 시점 일관성을 보장한다는 뜻은 아닙니다. 서비스가 무엇을 약속하는지, 변경이 어떤 페이지에 반영되는지 문서와 실제 계약으로 확인해야 합니다. 이번 실험은 페이지 수집의 실패 모양을 분리해서 보여 주며, 특정 업체의 API에 누락이 있다고 판정하거나 운영 수집기를 인증한 결과가 아닙니다.
자신의 작업을 점검할 때는 우선 이 예제의 9행과 고유 ID 8개를 대조해 보세요. 다음으로 빈 페이지에서 계속 읽는 이유와 반복 토큰에서 멈추는 이유를 각각 확인하면 됩니다. 마지막에는 수집 완료가 어느 시점의 어떤 목록을 뜻하는지 한 문장으로 적어 보세요. 그 문장이 명확해야 빠진 자료를 찾을 때 필요한 기준 원장과 추가 확인도 정할 수 있습니다.
입력과 결과를 직접 대조하는 재현 자료
실험 실행일은 2026년 10월 15일이며 이 글의 발행 일정과 구분합니다. Python 3 표준 라이브러리로 가상 자료만 처리했습니다. 아래 파일은 기존 사용자 파일을 인자로 받지 않습니다.
python3 api-pagination-overlap-ledger-lab-20261015.py실행 시각과 런타임은 환경에 따라 달라집니다. 판정 근거는 다운로드 결과의 고정 입력과 각 사례의 실제 출력으로 확인하세요.
규칙을 확인한 공식 원문
아래 원문은 2026년 10월 15일 확인했습니다. 형식과 동작의 설명을 확인한 출처이며, 이 글의 가상 실험이나 실제 업무 성과를 대신 증명하지 않습니다.
- Google AIP-158: Pagination — 빈 페이지여도 다음 토큰이 있을 수 있으며, 종료는 빈 next_page_token으로 판단하는 계약을 확인했습니다.
- GitHub REST pagination — 응답 Link 헤더를 따라 다음 페이지를 읽는 방식을 확인했습니다.
- PostgreSQL LIMIT and OFFSET — 정해진 순서가 없을 때 부분 결과의 예측 가능성과 OFFSET 처리의 한계를 확인했습니다.



