왜
dev 에서 https://df 를 등록한 건(trace 3f8fd4ab9ab9fd2f9278a1a324c88629, 2026-08-15T16:06:29Z)이 이렇게 흘렀다.
InternalHostGuard.verify 가 host 조회에 실패해 UnknownHostException 을 받는다
- 그것을
PageFetchException.upstreamError() 로 번역한다 (code UPSTREAM_ERROR, permanent=false) → 502
- core 의
RemoteExtractionContract.translate 는 422 만 확정 실패로 보므로 502 는 무조건 일시 실패다 → RETRYABLE 로 재시도
- 두 번째 시도도 같은 지점에서 실패 →
item.parse.result result=failed reason=retry_exhausted
어긋나는 것이 둘이다.
운영 사유가 실제와 다르다. retry_exhausted 는 "외부가 불안정했다, 재시도 여력이나 대상 장애를 보라"는 신호인데 실제 사유는 "사용자가 존재하지 않는 주소를 넣었다"이다. 운영 액션이 정반대라, reason 을 운영 액션 기준으로 재분류한 TeamPiKi/core#938 의 취지와 어긋난다.
확정적으로 실패할 주소에 매번 호출 2회를 태운다. 같은 https://df 가 2026-08-01 과 2026-08-15 두 번 등록됐고(canonical_hash 로 item 19 재사용), 두 snapshot(32, 344) 모두 attempt_count=2 를 소진한 뒤 FAILED 로 끝났다. host 가 resolve 되지 않는다는 사실은 재시도로도 재등록으로도 바뀌지 않는다.
무엇을
InternalHostGuard 가 host 조회 실패를 확정 실패로 내린다.
UnknownHostException 을 PageFetchException 의 확정 팩토리로 번역한다 (code INVALID_URL, permanent=true, escalatable=false).
- code 는 기존
INVALID_URL 을 재사용한다. 카탈로그에 이미 disposition: permanent, bucket: not_product 로 있고 core 의 PERMANENT_TRANSLATIONS 도 notProductPage() 로 매핑해 두었다. 따라서 core 변경은 없다 — extractor 가 code 를 바꿔 내리는 순간 core 는 재시도 없이 즉시 markFailed 로 끝낸다.
- escalatable=false — resolve 되지 않는 host 는 헤드리스 브라우저도 같은 이유로 도달하지 못한다. "SSRF(
BLOCKED_HOST)만 무조건 폴백의 예외"라는 현재 정책에 두 번째 예외가 생기므로, PageFetchException 의 그 주석과 카탈로그 주석을 함께 고친다.
- infra 카탈로그는
INVALID_URL 엔트리에 escalatable: false 를 더한다(additive). 이 code 가 이제 fetch 경로에서도 나오기 때문이다. 머지 순서는 infra 먼저.
DNS 리졸버 일시 장애는 구분하지 않는다. InetAddress.getAllByName 은 NXDOMAIN 과 SERVFAIL, 타임아웃을 모두 같은 UnknownHostException 으로 던져 구분할 수 없다. 리졸버 장애는 박스 전역 문제라 그 자체로 별도 관측 대상이고, 그 짧은 창에서 확정 처리된 건은 사용자가 재등록하면 새 snapshot 으로 다시 시도된다. 정확한 구분이 필요해지면 RCODE 를 보는 리졸버 도입을 별건으로 다룬다.
검증
- 단위:
InternalHostGuard 가 UnknownHostException 을 받으면 permanent=true, escalatable=false 인 PageFetchException 을 던진다
- 통합: 조회 불가 host 로
POST /internal/extractions/link 를 호출하면 422 + {"code":"INVALID_URL"} 을 반환한다
- 카탈로그 대조:
ExtractionErrorCodeCatalogTest 가 shared-infra 카탈로그와 계속 일치한다
- 회귀: SSRF 차단(
BLOCKED_HOST)은 확정 + escalatable=false 를 그대로 유지한다
참고
- 실패 trace:
3f8fd4ab9ab9fd2f9278a1a324c88629
- 관련 좌표:
InternalHostGuard.java:38, PageFetchException.java, TeamPiKi/infra 의 contracts/extraction-error-codes.yaml
왜
dev 에서
https://df를 등록한 건(trace3f8fd4ab9ab9fd2f9278a1a324c88629, 2026-08-15T16:06:29Z)이 이렇게 흘렀다.InternalHostGuard.verify가 host 조회에 실패해UnknownHostException을 받는다PageFetchException.upstreamError()로 번역한다 (codeUPSTREAM_ERROR, permanent=false) → 502RemoteExtractionContract.translate는 422 만 확정 실패로 보므로 502 는 무조건 일시 실패다 → RETRYABLE 로 재시도item.parse.result result=failed reason=retry_exhausted어긋나는 것이 둘이다.
운영 사유가 실제와 다르다.
retry_exhausted는 "외부가 불안정했다, 재시도 여력이나 대상 장애를 보라"는 신호인데 실제 사유는 "사용자가 존재하지 않는 주소를 넣었다"이다. 운영 액션이 정반대라, reason 을 운영 액션 기준으로 재분류한 TeamPiKi/core#938 의 취지와 어긋난다.확정적으로 실패할 주소에 매번 호출 2회를 태운다. 같은
https://df가 2026-08-01 과 2026-08-15 두 번 등록됐고(canonical_hash 로 item 19 재사용), 두 snapshot(32, 344) 모두attempt_count=2를 소진한 뒤 FAILED 로 끝났다. host 가 resolve 되지 않는다는 사실은 재시도로도 재등록으로도 바뀌지 않는다.무엇을
InternalHostGuard가 host 조회 실패를 확정 실패로 내린다.UnknownHostException을PageFetchException의 확정 팩토리로 번역한다 (codeINVALID_URL, permanent=true, escalatable=false).INVALID_URL을 재사용한다. 카탈로그에 이미disposition: permanent, bucket: not_product로 있고 core 의PERMANENT_TRANSLATIONS도notProductPage()로 매핑해 두었다. 따라서 core 변경은 없다 — extractor 가 code 를 바꿔 내리는 순간 core 는 재시도 없이 즉시 markFailed 로 끝낸다.BLOCKED_HOST)만 무조건 폴백의 예외"라는 현재 정책에 두 번째 예외가 생기므로,PageFetchException의 그 주석과 카탈로그 주석을 함께 고친다.INVALID_URL엔트리에escalatable: false를 더한다(additive). 이 code 가 이제 fetch 경로에서도 나오기 때문이다. 머지 순서는 infra 먼저.DNS 리졸버 일시 장애는 구분하지 않는다.
InetAddress.getAllByName은 NXDOMAIN 과 SERVFAIL, 타임아웃을 모두 같은UnknownHostException으로 던져 구분할 수 없다. 리졸버 장애는 박스 전역 문제라 그 자체로 별도 관측 대상이고, 그 짧은 창에서 확정 처리된 건은 사용자가 재등록하면 새 snapshot 으로 다시 시도된다. 정확한 구분이 필요해지면 RCODE 를 보는 리졸버 도입을 별건으로 다룬다.검증
InternalHostGuard가UnknownHostException을 받으면 permanent=true, escalatable=false 인PageFetchException을 던진다POST /internal/extractions/link를 호출하면 422 +{"code":"INVALID_URL"}을 반환한다ExtractionErrorCodeCatalogTest가 shared-infra 카탈로그와 계속 일치한다BLOCKED_HOST)은 확정 + escalatable=false 를 그대로 유지한다참고
3f8fd4ab9ab9fd2f9278a1a324c88629InternalHostGuard.java:38,PageFetchException.java, TeamPiKi/infra 의contracts/extraction-error-codes.yaml