Skip to content

[FEATURE] Per-host hyperparameter + time-bound priority override (#380 Sub C) #383

Description

@juhy0987

부모

부모 이슈: #380 (메타)
의존: Sub B (#382 — Host signal scoring) 머지 후

배경

Sub B 의 cluster-wide signal weight 는 운영 시작점으로 충분하지만, host 마다 특성이 달라 동일 weight 적용이 부정확. 또한 운영자가 특정 host 를 일시적으로 High 또는 Low 로 강제하고 싶은 케이스가 있음 (예: 이벤트 발생 시 특정 host 우선순위 즉시 상승).

본 Sub 는 Sub B 의 scoring 위에 운영자 explicit override + per-host weight 책정을 얹는다.

작업 범위

1. `fetcher_rules.priority_config` JSONB 컬럼

기존 `fetcher_rules` 테이블에 신규 JSONB 컬럼 추가. host 단위 priority 운영 인터페이스 단일화.

스키마 (JSON):
```json
{
"base_priority": "high",
"override_until": "2026-05-20T00:00:00Z",
"signal_weights": {
"freshness": 1.5,
"impact": 1.0,
"cost": 0.5,
"reliability": 1.0,
"host_trust": 1.2
},
"score_threshold": 0.55
}
```

  • `base_priority` — 명시적 override (high / normal / low) — 가장 강한 신호
  • `override_until` — 만료 시각 (ISO8601). 만료 후 자동 무효화 (time-bound 사용자 결정)
  • `signal_weights` — 본 host 의 weight 다중 (default 는 env cluster-wide weight)
  • `score_threshold` — 본 host 의 High vs Normal 분기 임계값 (default env)

모든 필드는 optional — 부분 override 가능.

2. `OverridePriorityResolver` 신규

Resolver chain 의 RetryPriorityResolver 다음, CategoryBased 이전 위치 (메타 #380 의 chain 참조).

```go
func (r *OverridePriorityResolver) Resolve(job *core.CrawlJob) core.Priority {
cfg := r.loadConfig(job.Host()) // Redis cache + DB fallback
if cfg == nil { return ... }
if cfg.OverrideUntil.Before(time.Now()) { return ... } // 만료
return cfg.BasePriority
}

func (r *OverridePriorityResolver) CanResolve(job *core.CrawlJob) bool {
cfg := r.loadConfig(job.Host())
return cfg != nil && cfg.BasePriority != "" && cfg.OverrideUntil.After(time.Now())
}
```

3. Sub B scorer 의 per-host weight 활용

`DynamicScorePriorityResolver` (Sub B) 가 score 계산 시:

  1. host 의 `priority_config.signal_weights` 가 있으면 그 weight 사용
  2. 없으면 env 의 cluster-wide weight 사용

`score_threshold` 도 동일하게 host-override 가능.

4. 운영 인터페이스 — env-only (사용자 결정)

운영 CLI 는 본 Sub scope 외. 운영자는 SQL 또는 향후 admin 도구로 `fetcher_rules.priority_config` 를 직접 갱신.

env 는 default 만 노출 (cluster-wide weights / threshold).

5. Time-bound 만료 처리

  • Resolver hot path 에서 매번 `override_until` 비교 — Redis cache TTL 을 `min(override_until, 5분)` 으로 설정해 만료 시점 +5분 안에 자동 invalidate
  • 또는 별도 cleaner goroutine 이 만료된 override 를 `base_priority=NULL` 로 자동 정리

6. 테스트

  • per-host weight override 동작 (signal weight 변경 시 score 차이)
  • time-bound 만료 (`override_until` 과거 → ignore)
  • 부분 override (signal_weights 만 / base_priority 만 / threshold 만)
  • backward compat — priority_config 없는 host 는 Sub B 의 cluster-wide 동작 그대로

운영 결정 (사용자 확정)

  • 가중치 → per-host 책정 가능
  • 운영 인터페이스 → env-only (default cluster-wide weight + threshold)
  • Manual override → time-bound 지원

영향 / 위험

효과

  • 운영자가 특정 host 의 priority 를 즉시 조정 (장애 / 이벤트 대응)
  • host 특성에 맞는 weight 책정 — 신뢰도 높은 host 의 score 가 더 정밀해짐

위험

  • JSONB 컬럼 검증 (스키마 강제 X) — application 측 unmarshal 시 graceful degradation 필요
  • Override 가 끝없이 유지되면 dynamic scoring 의도 무력화 — time-bound 강제 / 자동 만료로 완화

롤백

  • `priority_config` 컬럼 무시 → Sub B 동작 그대로
  • Override 정리는 단순 SQL UPDATE

완료 조건

  • `fetcher_rules.priority_config` JSONB 컬럼 + migration
  • `OverridePriorityResolver` + chain 등록
  • Sub B 의 scorer 가 per-host weight 활용
  • Time-bound 만료 처리 (Redis TTL or cleaner)
  • 단위 테스트
  • 운영 가이드 — README 또는 docs (override SQL 예시)

참고

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