부모
부모 이슈: #380 (메타)
의존: Sub A (#381 — CategoryBased + Retry) 머지 후
배경
Sub A 의 정적 카테고리 매핑은 host metadata 가 부재한 신규 host / 미분류 host 에 대해 모두 Low 로 fallback. 또한 같은 카테고리 안에서도 host 별 신뢰도 / 비용 / 시의성이 다른데 일률적으로 동일 priority 부여됨.
본 Sub 는 host 단위 signal 을 누적하여 High 와 Normal 사이의 동적 분기를 자동화한다. Low 후보 (Sub A 가 결정) 는 본 scoring 대상 X — Low → Normal/High 승급 없음.
Scoring 모델
입력 signal (host 단위, 시계열 누적)
| Signal |
의미 |
출처 |
| `freshness` |
publish→detect lag — 짧을수록 높음 |
metric (validator 의 published_at vs created_at) |
| `impact` |
카테고리 type 가중치 (정치/경제=高, 연예=中) |
host metadata + 카테고리 매핑 (Sub A 와 공유) |
| `cost` |
chromedp 비율 + LLM 재학습 빈도 + 평균 fetch latency — 비용 ↑ → 점수 ↓ |
metrics registry |
| `reliability` |
host_breaker 발동율 + HTTP 4xx/5xx 비율 — 안정 ↑ → 점수 ↑ |
circuit breaker + metrics |
| `host_trust` |
호스트 누적 신뢰도 (passed/rejected validation 비율, 운영자 평가) |
DB aggregate |
집계
score = w_fresh × freshness
+ w_impact × impact
+ w_trust × host_trust
− w_cost × cost
− w_reliability_penalty × (1 − reliability)
- score 는 [0, 1] 정규화
- weight 는 cluster-wide env (per-host 는 Sub C 에서 확장)
- mapping 은 High vs Normal 2-tier 만: score ≥ θ → High, 그 외 → Normal
- θ (threshold) 도 env 노출 (default 0.5)
Cold-start
신규 host 는 signal 누적 부족 → score 계산 skip → Sub A 의 결정에 그대로 위임 (대부분 Low 또는 매핑된 카테고리)
인프라
1. 신규 테이블 `host_scoring_state`
```sql
host TEXT PRIMARY KEY,
score REAL, -- 0~1
signals JSONB, -- 입력값 스냅샷
window_minutes INT,
calculated_at TIMESTAMPTZ
```
2. Scorer goroutine
- 5분 주기 polling (env: `SCORING_INTERVAL`, default 5m)
- 모든 host (또는 active subset) 에 대해 signal 집계 → score 계산 → state 갱신
- 별도 stage (parserStage 류) 또는 main goroutine 내 부속
3. Redis cache
- key: `host_score:` → JSON `{score, priority, calculated_at}`
- TTL: scorer 주기와 동일 (5분)
- Resolver 핫패스가 직접 lookup (1ms 이하)
4. `DynamicScorePriorityResolver`
- Redis lookup → 없으면 DB lookup → 둘 다 없으면 CanResolve=false (다음 resolver 로 위임)
- score 가 있어도 카테고리가 명시적으로 Low 인 job 은 본 resolver 거치지 않음 (Sub A 가 chain 상 먼저 결정)
Resolver chain 위치
Sub A 의 CategoryBased 다음 — Low 가 이미 결정된 job 은 본 resolver 진입 안 함:
```
... → CategoryBasedResolver → DynamicScorePriorityResolver → SourcePriorityResolver → ...
```
CategoryBased 가 Normal 또는 High 매핑한 후 본 resolver 가 host 신호로 재조정.
단, 본 resolver 가 결정한 후 chain 종료 (이후 resolver 는 skip) — score 결정이 가장 정밀한 신호.
운영 결정 (사용자 확정)
- Weight 운영 인터페이스: env-only
- per-host weight 는 Sub C 의 scope
- Low 후보는 그대로 (scoring 대상 X)
영향 / 위험
효과
- 같은 카테고리 안에서도 신뢰도/비용 기반 정교한 분배
- 비용 높은 host (chromedp/LLM 비중 大) 는 Normal 로 강등 → high pool 의 자원 보존
- 시의성 높은 host (실시간 update 多) 는 High 로 승급
위험
- Scoring 인프라 자체 비용 (DB row scan + Redis IO + goroutine) — 5분 주기 cap 으로 흡수
- Signal 수집의 정확성 의존 — metrics registry 의 host dim 확장 필요
- Cold-start 처리 (score 없음 → CategoryBased 결정 그대로) 가 운영자 기대와 다를 수 있음
완료 조건
참고
부모
부모 이슈: #380 (메타)
의존: Sub A (#381 — CategoryBased + Retry) 머지 후
배경
Sub A 의 정적 카테고리 매핑은 host metadata 가 부재한 신규 host / 미분류 host 에 대해 모두 Low 로 fallback. 또한 같은 카테고리 안에서도 host 별 신뢰도 / 비용 / 시의성이 다른데 일률적으로 동일 priority 부여됨.
본 Sub 는 host 단위 signal 을 누적하여 High 와 Normal 사이의 동적 분기를 자동화한다. Low 후보 (Sub A 가 결정) 는 본 scoring 대상 X — Low → Normal/High 승급 없음.
Scoring 모델
입력 signal (host 단위, 시계열 누적)
집계
Cold-start
신규 host 는 signal 누적 부족 → score 계산 skip → Sub A 의 결정에 그대로 위임 (대부분 Low 또는 매핑된 카테고리)
인프라
1. 신규 테이블 `host_scoring_state`
```sql
host TEXT PRIMARY KEY,
score REAL, -- 0~1
signals JSONB, -- 입력값 스냅샷
window_minutes INT,
calculated_at TIMESTAMPTZ
```
2. Scorer goroutine
3. Redis cache
4. `DynamicScorePriorityResolver`
Resolver chain 위치
Sub A 의 CategoryBased 다음 — Low 가 이미 결정된 job 은 본 resolver 진입 안 함:
```
... → CategoryBasedResolver → DynamicScorePriorityResolver → SourcePriorityResolver → ...
```
CategoryBased 가 Normal 또는 High 매핑한 후 본 resolver 가 host 신호로 재조정.
단, 본 resolver 가 결정한 후 chain 종료 (이후 resolver 는 skip) — score 결정이 가장 정밀한 신호.
운영 결정 (사용자 확정)
영향 / 위험
효과
위험
완료 조건
참고