부모
부모 이슈: #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 계산 시:
- host 의 `priority_config.signal_weights` 가 있으면 그 weight 사용
- 없으면 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
완료 조건
참고
부모
부모 이슈: #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
}
```
모든 필드는 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 계산 시:
`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 만료 처리
6. 테스트
운영 결정 (사용자 확정)
영향 / 위험
효과
위험
롤백
완료 조건
참고