Skip to content

[FEATURE] host 단위 fetcher rule + validation 기반 자동 chromedp 전환 + 실패 raw republish #175

Description

@juhy0987

어떤 기능인가요?

현재 fetcher 파이프라인의 문제:

  • 거의 모든 HTML 이 goquery 단계에서 종료 됩니다 (chain 의 lazy-load detect + browser fallback 이 일부 cover 하지만 충분치 않음)
  • SPA / dynamic content 사이트의 HTML 은 "형식적으로는 받아왔으나 본문이 비어있는" 상태로 통과 — parsing 까지 가서야 빈 본문 발견
  • 실패 후에도 동일 host 의 다음 fetch 가 또 goquery 로 시작 → 동일 실패 반복

본 이슈는 host 단위로 fetcher 선택 정책 을 도입하고, validation 단계의 실패 누적을 신호로 자동으로 chromedp 로 전환 + 이전 실패 raw 들을 다시 fetch 하여 정상 처리 하는 자가 학습 흐름을 구현합니다.

제안된 흐름

```

  1. 새 host 의 첫 fetch → fetcher rule 부재 → default goquery 로 진행
  2. parser_worker 가 정상 파싱 → 정상 흐름 (변경 없음)
  3. parser_worker 가 parse_failure / 빈 본문 감지 →
    • host 단위 실패 카운터 증가 (Redis sliding window)
    • 임계값 (예: 최근 1h 내 5회) 도달 시:
      a. fetcher_rules 테이블에 host → chromedp UPSERT
      b. 같은 host 의 실패 raw_id 들을 모아서 retry job 으로 Kafka publish
      (TopicCrawl* 의 새 메타데이터 "force_fetcher=chromedp" 부착)
      c. 이후 같은 host 의 새 fetch 도 fetcher_rules 조회 결과대로 chromedp 사용
  4. chromedp 로 retry → 정상 파싱 → contents 저장
    ```

무엇을 하나요?

본 이슈는 한 PR 에 다 담기엔 큼 — 단계 분리 후 별도 PR.

단계 1 — host 단위 fetcher rule 인프라 (PR 1)

DB 스키마

```sql
CREATE TABLE fetcher_rules (
id BIGSERIAL PRIMARY KEY,
host_pattern TEXT NOT NULL UNIQUE,
fetcher TEXT NOT NULL, -- 'goquery' | 'chromedp'
reason TEXT, -- 'manual' | 'auto_upgrade_validation' | ...
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX idx_fetcher_rules_host ON fetcher_rules(host_pattern);
```

코드 변경

  • `storage.FetcherRuleRepository` 인터페이스 + Postgres 구현
  • `fetcher.Resolver` 신규 (rule.Resolver 와 유사 — host 단위 cache)
  • ChainHandler 가 fetch 직전 `fetcher.Resolver.Resolve(host)` 호출 → 결과에 따라 chain 구성 분기
    • 결과 없음 → default chain (goquery 우선 + lazy-load fallback) — 기존 동작
    • 결과 "chromedp" → chromedp 단일 chain (goquery 시도 skip)

호환성

  • 기존 host 는 fetcher_rules 부재 → default 동작 → 현재 동작 100% 보존

단계 2 — 실패 감지 + 카운팅 (PR 2)

실패 신호 정의

  • `rule.Error{Code: ErrParseFailure}` (selector 매칭 0건)
  • 본문 빈 결과 (Title / MainContent 매칭됐으나 텍스트 길이 < 임계값) — 새 신호 정의 필요
  • target_type=page 만 카운팅 (list 는 맥락이 다름)

카운터 (Redis sliding window)

```
key: fetcher:fail:{host}
TTL: 1h (sliding)
```

  • parser_worker 의 `handleRuleError` / 빈본문 분기에서 호스트별 카운터 INCR
  • INCR 후 값 ≥ 임계값 + 현재 fetcher_rules 부재 (또는 'goquery') → 단계 3 의 upgrade 트리거

환경변수

  • `FETCHER_AUTO_UPGRADE_ENABLED`: true (default true)
  • `FETCHER_AUTO_UPGRADE_THRESHOLD`: 5 (default — 1h 윈도우 내 실패 횟수)
  • `FETCHER_AUTO_UPGRADE_WINDOW`: 1h

단계 3 — chromedp 전환 + 실패 raw republish (PR 3)

전환 흐름

```

  1. 임계값 도달 감지 (단계 2)
  2. fetcher_rules UPSERT: host → 'chromedp', reason='auto_upgrade_validation'
  3. 같은 host 의 실패 raw_id 목록 수집:
    • raw_contents 에서 SELECT (where host=$1 AND parse_failed_at IS NOT NULL)
    • 또는 별도 `failed_raws` 테이블 / Redis SET 으로 추적
  4. 각 raw_id 에 대해 새 CrawlJob 생성:
    • URL = raw_contents.url
    • target_type = 원본과 동일
    • Headers = {"force_fetcher": "chromedp", "retry_reason": "validation_upgrade", "original_raw_id": ...}
  5. TopicCrawl* 으로 publish — 새 fetch 사이클 진입
  6. ChainHandler 가 "force_fetcher" 헤더 우선 (fetcher_rules 보다 우선)
  7. 새 fetch 정상 처리 → 새 raw 저장 + parser 정상 처리
  8. 원본 raw 는 cleanup cron 이 TTL 후 정리 (이미 동작 중)
    ```

안전망

  • 재upgrade 중복 방지: fetcher_rules 가 이미 'chromedp' 면 무시
  • republish flooding 방지: 같은 host 의 republish 는 in-flight dedup (단일 트리거 사이클로 제한)
  • chromedp 도 실패 시: 단계 2 의 카운터 가 chromedp 결과로 다시 INCR → 임계값 도달 → fetcher_rules 가 이미 chromedp 라 변경 없음. alert/audit log 필요 — 운영자 개입 필요한 host 식별

단계 4 — 운영 도구 (PR 4 또는 #172 와 합류)

결정 / 모호 영역 (PR 본문에서 구체화)

항목 후보
카운팅 단위 host 만 (본 이슈) vs (host, target_type) vs (host, path)
"빈 본문" 임계값 Title 또는 MainContent 길이 < 100 chars? 별도 결정
임계값 / 윈도우 5회 / 1h (default) — 운영 데이터 보고 조정
양방향 전환 본 이슈 = chromedp 전환만 (upgrade-only). 다시 goquery 로 downgrade 는 별도 이슈 (chromedp 비용 ↑ 라 자동 downgrade 위험)
fetch republish 대상 (a) 직전 N건 실패 raw 만 / (b) 같은 host 의 미정리 raw 전체 — (a) 권장 (안전)
force_fetcher header 보안 외부 source 가 임의 fetcher 강제 못하도록 internal-only validation

의존 / 연관

영향 / 위험

효과

위험

  • chromedp 비용 폭주: false positive 로 잘못 upgrade 된 host 가 chromedp 로 fetch 계속 → 응답 시간 / 메모리 ↑. 임계값 보수적 (default 5회) + audit log 로 가시화
  • republish 의 chained job 폭증: 한 host 에 실패 raw 100건 있으면 republish 100건 동시 발생. cap (예: max 20건/upgrade 사이클) + 시간 분산 필요
  • upgrade 후 chromedp 도 실패 시 무한 reprocess 위험: 이미 chromedp 면 추가 republish 안 함 (안전망). alert 로 운영자 개입 유도
  • fetcher_rules 와 parsing_rules 의 lifecycle 분리: 운영자 헷갈림 가능. CLI 도구로 통합 view 제공 권장

롤백

  • 환경변수 `FETCHER_AUTO_UPGRADE_ENABLED=false` → 즉시 자동 전환 비활성. 이미 chromedp 인 host 는 유지 (manual override 만 변경)
  • 코드 revert: PR 별 분리 revert. 단계 1 만 남기면 운영자 manual fetcher rule 설정 가능
  • DB rollback: `DROP TABLE fetcher_rules` (down migration)

참고

  • 본 이슈의 background:
    • 운영 debug.log 분석 결과 — chromedp 자동 fallback 59건 / browser fetch 실패 21건 / circuit breaker 99건 — fetcher 선택의 1차 결정이 host 별로 달랐어야 함을 시사
    • chain 의 lazy-load detect 는 일부 cover 하지만 "정적 HTML 받았으나 본문 없음" 케이스는 못 잡음
  • 관련 코드:

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions