본문으로 이동

JSON 형식의 백링크 데이터: $100 무료 크레딧, 카드 불필요, Google 로그인.

SEOMasters.dev
SEOMasters.dev회원가입

URL 백링크 체커를 공백이나 무한 루프 없이 페이지네이션하세요

호출당 행 수, 쿼리당 페이지 수, 그리고 50,000의 하드 상한선. 이 세 가지를 모두 준수하고 언제 멈춰야 하는지 알려주는 루프입니다.

안개 낀 강 위에 간격 없이 이어지는 긴 돌다리 아치
기억해야 할 세 가지 숫자

호출당 최대 1,000행이 반환됩니다. 각 호출은 행 수에 관계없이 한 번 청구됩니다. 심층 페이지네이션은 50,000행으로 제한되며, 이는 1,000행씩 50회 호출에 해당합니다.

페이지네이션보다 정렬이 우선해야 하는 이유

정렬되지 않은 목록에 대해 페이지네이션하면 중복이 발생합니다. 항상 sort와 order를 page와 per와 함께 전송하여, 실행 중 모든 요청에서 페이지 3이 동일한 의미를 가지도록 하세요.

refdomains 조회는 호출당 2크레딧이므로, 50회 호출 시 100크레딧, 즉 1센트가 됩니다. 최악의 경우에도 비용은 저렴하지만, 실제 비용은 종료되지 않는 루프에서 발생합니다.

정렬 열 선택

어떤 열로 정렬할지는 질문에 따라 다릅니다. 감사 작업의 경우 일반적으로 가장 큰 값부터 정렬하는 것이 좋습니다. 이렇게 하면 상한선이 당신이 관심 있는 부분이 아닌 긴 꼬리 부분을 잘라내게 됩니다.

질문과 무관한 필드로 정렬하면, 잘라낸 부분에서 원하는 행이 제거될 수 있습니다. 후보가 되는 각 정렬 방식으로 10행을 먼저 가져와서, 어떤 슬라이스가 상한선을 견디는지 확인하세요.

이 선택 사항을 보고서에 기록하여, 다음 사람이 상한선이 어디에서 적용되었는지 그 이유를 알 수 있도록 하세요.

실행 중 이동하는 레코드

월간 재빌드가 실행 중간에 완료되지 않는 한 데이터는 이동하지 않습니다. 드물지만 실제로 발생할 수 있는 상황입니다. 월 초를 가로지르는 야간 작업은 이전 인덱스의 한 페이지와 새 인덱스의 다음 페이지를 반환할 수 있으므로, 로그에 시작 시간을 기록하고 비교하세요.

로그 상단에 시작 시간을 기록하고 각 행에 페이지 번호를 기입하세요. 그러면 출력의 불일치가 파싱 버그가 아닌 타이밍 문제임을 나타냅니다.

작업이 예약된 경우, 재빌드가 발생할 가능성이 높은 시간 직전에 시작 시간을 설정하지 마세요. 로그에서 재빌드는 일치하는 두 실행 간 총량의 변화로 나타나므로, 모든 로그 행에 총량을 유지하세요.

재빌드를 가로지르는 실행은 기본적으로 잘못되지 않았습니다. 일관성만 없을 뿐이므로, 보고서에 중요한 경우 해당 슬라이스를 다시 실행할 수 있도록 로그에 이러한 실행을 표시하세요.

종료되는 페이지네이션 루프

Node 20 워커용 TypeScript. 첫 번째 짧은 페이지, 상한선 도달, 또는 ok가 false인 경우 중단됩니다.

ts
const BASE = "https://seomasters.dev/api.php";
const CAP = 50_000;
const PER = 1000;

export async function pullAll(domain: string, key: string) {
  const rows: unknown[] = [];
  for (let page = 1; rows.length < CAP; page++) {
    const u = new URL(BASE);
    u.search = new URLSearchParams({
      action: "refdomains", q: domain,
      page: String(page), per: String(PER),
      sort: "dr", order: "desc",
    }).toString();
    const res = await fetch(u, { headers: { Authorization: `Bearer ${key}` } });
    if (res.status === 401 || res.status === 402) throw new Error(`stop: HTTP ${res.status}`);
    const body = await res.json();
    if (!body.ok) throw new Error(JSON.stringify(body).slice(0, 300));
    const chunk = findRows(body);   // see note below
    rows.push(...chunk);
    if (chunk.length < PER) break;  // short page means we are done
  }
  return rows;
}

페이지네이션 실행 문제 해결

관찰된 현상추정 원인대응 방안
페이지 51이 비어 있거나 오류를 반환함50,000 상한선에 도달함다른 작업이나 다른 sort로 범위를 좁힌 후 id로 병합
두 페이지에 동일한 도메인정렬되지 않은 쿼리 또는 실행 중 인덱스 재빌드sort와 order 전송; 도메인 키에서 중복 제거
대시보드 총행 수보다 적은 행총량은 전체 그래프에 대한 것이며, 조회는 상한선이 있음UI에 "상위 50,000" 표시
루프가 종료되지 않음조건 확인이 rows.length만 수행함위와 같이 짧은 페이지에서 중단
예상보다 높은 청구 금액모든 비-200 응답에 대해 재시도네트워크 오류에만 재시도, errors 참조
햇살이 비치는 온실에서 고유한 묘목을 점검하는 정원사

필드 이름을 추측하지 않고 행 읽기

위의 findRows 헬퍼는 의도적으로 개방되어 있습니다. 실제 호출을 한 번 수행한 후 JSON을 확인하고, 튜토리얼이 약속한 것이 아니라 실제로 확인한 내용에 기반하여 접근자를 작성하세요.

이 작업의 저렴한 대안인 페이지네이션 없이 여러 도메인을 한 번에 평가하는 작업에 대해서는 bulk DR script를 읽으세요.

응답을 고정으로 동결

그런 다음 테스트로 동결하세요. 하나의 응답을 고정 파일로 저장하고 행 수와 첫 번째 행의 키에 대해 어서션하여, 향후 변경 사항이 고객 차트에서가 아닌 CI에서 실패하도록 하세요.

테스트와 함께 버전 관리 시스템에 고정 파일을 유지하여, 아무것도 실행하지 않고도 검토자가 코드가 기대하는 구조를 확인할 수 있도록 하세요.

API가 나중에 필드를 추가하면, fixture diff는 해당 accessor가 이를 인지했는지 아니면 묵살했는지를 알려줍니다.

쿼리가 발생한 액션, 도메인, 정렬 방식 등을 반영해 fixture에 이름을 붙이세요. response1.json과 같은 파일명은 다음 독자에게 그것이 무엇을 증명하는지 아무것도 알려주지 않습니다.

저장한 행의 개수를 세세요.

저장한 행과 루프가 보고한 총 개수를 비교하세요. 차이가 발생하면 페이지가 누락된 것이며, 중복이 발생하면 페이지가 두 번 가져와져 하나의 행으로 upsert되었음을 의미합니다.

첫 번째 추출 이후뿐만 아니라 모든 테스트 추출 후 이 검사를 실행하세요. 개수의 변화는 보통 정렬 방식 변경이나 재빌드가 실행 중일 때 발생하는 첫 번째 증상입니다.

페이지 번호와 동일한 로그 라인에 개수를 유지하세요. 그래야 두 수치를 한눈에 함께 읽을 수 있습니다.

상한선 위에서의 작업 분할

일부 쿼리는 상한선을 초과합니다. 상한선 이상의 목록의 경우, 더 많은 페이징을 시도하기보다 문제를 분할하세요. 하나의 정렬 방식으로 상위 50,000개를 가져온 후, 다른 액션에 대해 주요 도메인(heavy hitters)에 대해 질문하세요. 이러한 도메인도 2크레딧이 필요하며 서로 다른 질문에 대한 답변을 제공하기 때문입니다. 페이징을 더 많이 한다고 해서 컷오프를 넘을 수 없습니다.

하나의 정렬 방식으로 상위 50,000개를 가져오세요.

API의 상한선을 사실로 받아들이세요. 하나의 정렬 방식으로 상위 50,000개의 행을 가져와 저장하고, 보고에서 해당 결과를 '상위 슬라이스(top slice)'로 표시하세요.

상한선으로 제한된 개수를 전체 총수인 것처럼 보여주는 대시보는 이를 읽는 사람을 오도합니다. 라벨링에는 비용이 들지 않으며, url backlink checker 보고서는 이를 포함할 때 더 정직하게 읽힙니다.

실행 전에 슬라이스 크기를 결정하고 보고서 헤더에 기록하세요. 그래야 숫자가 실제보다 크게 보이지 않습니다. 헤더 라인에는 비용이 들지 않으며, 나중에 누락된 행을 발견한 독자는 이를 감사할 것입니다.

주요 도메인에 다른 질문을 던지세요

anchors와 toppages는 어떤 anchor text가 지배적인지, 또는 어떤 페이지가 링크를 유도하는지 등 다른 질문에 대한 답변을 제공합니다. 상한선을 초과한 도메인의 경우, 이러한 답변이 동일한 목록의 다음 페이지보다 더 중요할 수 있습니다.

도메인 키를 키로 사용하여 결과를 별도의 테이블에 유지하세요. 그래야 동일한 도메인의 후속 추출 시 상위 슬라이스 행을 건드리지 않고 이를 대체할 수 있습니다.

추가 액션마다 별도의 크레딧이 필요하므로, 청구서가 도착한 후가 아니라 실행 전에 가격을 책정하세요.

매월 한 번 추출된 가장 많은 도메인 목록은 매일 밤 추출된 긴 목록보다 더 유용한 컨텍스트를 제공하는 경우가 많습니다.

도메인 키로 병합(Merge)

두 추출이 겹칠 경우, 도메인 키로 병합하고 최신 타임스탬프를 유지하세요. 그러면 병합은 어느 추출이 먼저 완료되었는지에 의존하지 않습니다.

각 행에서 어떤 추출이 승리했는지 기록하세요. 만약 두 보고서 간에 숫자가 변경되었다면, 해당 로그는 인덱스인지 병합인지가 변경되었는지를 알려줍니다.

두 추출이 만나는 유일한 곳인 병합 로직을 테스트가 포함된 단일 함수로 유지하세요.

크래시 후 재개

크래시는 발생합니다. 배포로 인해 워커가 재시작되거나, 노트북이 절전 모드로 들어가거나, VPN이 끊기면 루프가 50페이지 중 31페이지에서 죽을 수 있습니다.

모든 호출 후 페이지 저장

모든 호출 후 마지막 완료된 페이지 번호를 영구 저장하세요. 맨 위에서가 아니라 해당 번호의 다음 페이지부터 재시작하면, 전체 추출을 다시 구매하는 대신 페이지당 2크레딧만 지불하면 됩니다.

응답이 파싱된 후에 저장하세요. 읽지 못한 페이지는 다시 가져와야 하며, 저장된 페이지 번호는 항상 행이 존재함을 의미해야 합니다.

매 호출마다가 아닌 분 단위로 저장하면, 크래시로 인해 크레딧이 중복 지출될 수 있는 창이 생깁니다. 저장을 작게 유지하여 매번 실행해도 저렴하게 처리되도록 하세요.

각 저장과 각 호출에 걸린 시간을 모두 로그에 기록하세요. 호출 시간과 맞먹는 저장 시간이면 스토리지 레이어를 가리키며, 이는 루프가 커지기 전에 수정할 가치가 있습니다.

도메인 키로 Upsert

Idempotent한 작성을 유지하세요. 도메인 키를 기준으로 각 행을 upsert하면, 두 번 가져온 페이지가 두 행이 아닌 하나의 행으로 남습니다.

수동 수리 스크립트를 포함해 모든 쓰기 경로에서 동일한 키를 사용하세요. 두 키를 가진 두 쓰기 경로는 재개 시 중복이 생존하는 원인이 됩니다.

수리 스크립트는 종종 실패한 실행 후 급하게 작성됩니다. 이때 두 번째 키가 숨어들기 쉬우므로, 메인 루프만큼 신중하게 검토하세요.

정렬 방식이 변경된 경우 재개 거부

페이지 번호와 함께 정렬 방식과 순서를 로그에 기록하세요. 만약 크래시와 재시작 사이에 sort가 변경되었다면, 저장된 페이지 번호는 동일한 슬라이스를 더 이상 의미하지 않습니다. 재개를 거부하고 깨끗하게 시작하세요.

정렬 방식, 순서, 페이지 번호 및 시간을 포함하는 작은 재개 레코드를 저장하세요. 루프는 해당 레코드를 먼저 읽으며, 요청하려는 요청과 필드가 더 이상 일치하지 않으면 계속 진행하지 않습니다.

거부된 재개는 실패가 아닙니다. 이는 저장된 페이지가 더 이상 동일한 행을 의미하지 않는다고 루프가 말하며, 깨끗하게 시작하는 것이 더 저렴한 실책입니다.

프로덕션에서 루프 실행

  1. 1

    작은 도메인으로 시작하세요

    수백 개의 행을 가진 도메인에 대해 루프를 실행하여, short-page stop이 발동하고 종료 경로가 작동하는지 확인하세요.

  2. 2

    한 페이지를 눈으로 확인하세요

    저장된 응답 중 하나를 열어 accessor가 여전히 행을 읽는지 확인하세요. 직접 확인하지 않은 파싱을 신뢰하지 마세요.

  3. 3

    제한값을 설정 파일에 유지하세요

    상한선과 페이지 크기를 하나의 설정 파일에 넣으세요. 그래야 변경 사항이 리뷰에서 나타나며 함수 내부에 숨지 않습니다.

  4. 4

    루프 종료 이유를 로그에 기록하세요

    실행이 short-page, cap 또는 오류로 인해 종료되었는지 기록하세요. 이 세 가지 종료 방식은 로그를 읽는 사람에게 서로 다른 의미를 가집니다.