본문으로 건너뛰기
JSL107's Tech Note
돌아가기

재시도할 가치가 없는 크롤 대상을 분류하기

7분 분량이 글 고치기
목차 · 6

학교 게시판에는 목록에 뜨지만 상세 내용은 읽을 수 없는 글이 있어요. 기존에는 이런 글도 일반 실패로 처리해 불필요한 재시도와 잘못된 운영 신호가 발생했고, 문제를 해결하려면 재시도 횟수가 아니라 실패 분류 체계를 바꿔야 했어요.

실패가 아닌데 실패로 집계되는 게시물

비공개 게시물은 상세 이동 핸들러에서 비공개 플래그가 활성화되면 경고창만 뜨고 상세 페이지로 넘어가지 않았어요. 주소도 그대로라 글 번호를 추출할 수 없었고, 크롤러는 이를 IDENTIFIER_MISSING 오류로 기록했어요.

삭제된 게시물의 상세 페이지를 요청하면 HTTP 404가 반환됐어요. 빈 페이지에서 값을 읽으면 undefined가 나왔고, 이후 저장 단계에서 REQUIRED_DATA_READ_FAILED로 처리됐어요.

두 경우 모두 다시 시도해도 결과는 달라지지 않아요. 하지만 기존 크롤러는 이를 일반 실패로 분류해 설정된 최대 횟수까지 재시도했어요. 데드레터 큐의 표본을 상당수 조사해 보니 특정 지역 계열의 게시판이 대부분이었고, 주요 오류도 이 두 종류에 몰려 있었어요.

매일 읽을 수 없는 게시물이 대거 실패로 처리되면서 재시도 규모가 몇 배로 늘어났어요. 일정한 주기로 실행돼야 할 크롤이 오후에는 수 시간 간격으로 밀리기도 했어요. 학교 게시판은 정상인데 대시보드에는 장애가 난 것처럼 표시되는 점도 문제였어요.

성공할 가능성이 없는 대상을 일반 실패와 구분하는 게 핵심이었어요.

실패와 제외를 분리하기

먼저 대시보드와 집계 모델에 excluded라는 새 분류를 추가했어요. 읽을 수 없는 글을 조용히 버리는 대신 실패 통계에서는 빼면서도 건수와 원인은 계속 살펴볼 수 있게 했어요.

분류 이름은 다음과 같이 역할에 맞춰 붙였어요.

배포 순서도 중요했어요. 크롤러가 새 제외 코드를 먼저 보내면 기존 대시보드는 이를 알아보지 못하고 미분류로 처리해요. 미분류 항목은 다시 조치 필요에 쌓이기 때문에 운영 신호가 오히려 나빠져요. 그래서 대시보드가 먼저 excluded 분류를 이해하도록 만든 뒤 크롤러의 판정 로직을 연결했어요.

크롤러는 잠기거나 삭제됐다는 근거가 확인된 행에 제외 오류 표식을 남겼어요. 해당 행은 재시도 대상인 실패 버킷이 아니라 excluded 버킷으로 옮겼고, 실행 요약에도 제외 건수를 담아 실제 실패와 구분했어요.

Crawl finished with 0 failure(s): rule=0, crawl=0, sync=0, excluded=<count>

이 구조를 적용하자 “크롤러가 고장 나서 못 읽었다”와 “게시물 상태 때문에 읽을 수 없다”를 서로 다른 운영 사건으로 표현할 수 있게 됐어요.

근거 없는 판정이 정상 게시물까지 제외한 회귀

초기 구현의 방향은 맞았지만 판정 시점과 근거가 충분하지 않았어요. 본 크롤보다 앞선 pre-read 단계에서 대상 읽기 함수의 결과를 보고 읽기 불가 여부를 판단했는데, 이때는 정상 게시물의 값이 아직 모두 렌더링되지 않았을 수 있었어요. 실제 검증 대상 게시판에서도 정상 게시물 모두에서 title만 undefined인 상태로 판정됐어요.

그 결과 HTTP 응답이 200이고 실제로 읽을 수 있는 게시물까지 Required read value is missing으로 제외됐어요. 바뀐 부분만 적용한 뒤 같은 규칙을 실행하자 결과가 분명하게 갈렸어요.

변경 전: Successfully crawled (<all> of <all>) rows!
ok=<all>, err=0
변경 후: <all> rows pre-read, crawled=0
Failed Crawling: missing rows must be crawled exist.

부분적으로 렌더링된 값을 “읽을 수 없는 게시물”의 증거로 삼은 게 원인이었어요. 값이 없다는 사실만으로는 게시물이 비공개인지, 삭제됐는지, 아직 렌더링되지 않았는지 구분할 수 없어요.

핫픽스에서는 근거 없는 판정을 없앴어요. 잠금 메시지나 HTTP 404처럼 게시물 상태를 직접 설명하는 신호가 있을 때만 제외하도록 제한했어요. 정상 게시물을 보호하려면 실패 분류의 이름보다 판정 근거와 시점을 먼저 검증해야 했어요.

제외만 있는 실행은 성공으로 처리하기

행 단위 분류를 고친 뒤에도 task 단위 판정에는 문제가 남아 있었어요. 운영 로그를 보니 실제 실패 없이 제외만 발생한 실행이 있었어요.

Crawl finished with 0 failure(s): rule=0, crawl=0, sync=0, excluded=<count>
Row excluded: Content is locked

하지만 이런 실행도 실패 task 저장소에 원인 불명 실패로 기록돼 워커가 재시도했어요. 행 수준에서는 excluded와 failure를 분리했지만, 실행 요약을 생성할지 판단하는 조건에 제외 건수까지 포함했기 때문이에요.

후속 수정에서는 실제 실패 건수만으로 task 실패 여부를 판단하도록 정리했어요. 비공개나 삭제 게시물이 있다는 사실은 요약과 대시보드에 남기되 rule, crawl, sync 실패가 모두 0이면 task 자체는 실패로 올리지 않아요.

분류 체계는 행과 task, 두 단계에 일관되게 적용했어요.

재시도 대상과 운영 신호를 정렬한 결과

최종적으로 비공개 게시물과 삭제된 게시물은 일반 크롤 실패에서 제외했어요. 잠금 또는 HTTP 404라는 명확한 근거가 있는 글만 제외하고, 제외 건수는 대시보드에서 계속 확인할 수 있어요.

제외만 있는 실행도 더는 원인 불명 task 실패로 기록되지 않아요. 크롤러가 성공적으로 처리할 수 있는 범위를 모두 처리했다면 읽을 수 없는 게시물이 있더라도 실행 자체는 성공으로 남아요.

이 변경으로 결과가 달라지지 않는 게시물에 반복하던 재시도를 없애고, 비공개·삭제 게시물과 실제 크롤 장애의 통계를 분리했어요. 제외만 있는 정상 실행을 task 실패로 잘못 기록하던 문제와 정상 게시물까지 제외하던 pre-read 판정 회귀도 고쳤어요. 제외 사유와 건수는 그대로 남겨 관측 가능성도 유지했어요.

다만 실제 오류율과 전체 크롤 대기시간이 얼마나 개선됐는지는 아직 측정하지 않았어요. 이번 작업에서는 분류와 재시도 동작이 의도대로 정리됐다는 점까지만 확인했어요.

실패 분류는 재시도 정책이자 운영 인터페이스다

크롤러가 모든 미수집을 같은 실패로 취급하면 구현은 단순해 보이지만 재시도 큐와 대시보드가 담는 의미는 금세 흐려져요. 복구 가능한 장애와 영구적으로 읽을 수 없는 대상, 아직 판정할 근거가 없는 상태를 구분해야 재시도 정책도 정확해져요.

제외는 단순한 예외 처리가 아니에요. 재시도 여부와 task 성공 판정, 대시보드 통계가 함께 달라져야 하는 도메인 상태예요. pre-read 단계의 undefined처럼 원인이 여러 가지일 수 있는 신호로 확정 판정을 내리면 정상 데이터까지 잃을 수 있으므로, 잠금 메시지나 HTTP 404처럼 원인을 직접 설명하는 증거가 필요해요.

새 분류를 도입할 때는 생산자와 소비자의 배포 순서도 따져야 해요. 크롤러가 새로운 상태를 만들기 전에 대시보드와 집계 계층부터 그 상태를 이해해야 해요.

재시도를 줄이는 가장 안전한 방법은 횟수를 낮추는 게 아니라 다시 시도할 가치가 없는 대상을 정확히 분류하는 거예요. 그 분류가 정상 데이터를 해치지 않는지도 변경 전후에 같은 조건으로 실측해 검증해야 해요.


이 글 고치기
이 글 공유하기:

이전 글
사람의 반박을 학습하는 PR 리뷰봇의 피드백 루프
다음 글
만료 파일 삭제를 안전하게 활성화하는 설계